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 @@ -44,6 +44,13 @@ CGSCCUpdateResult *UR = nullptr; ///} + /// Internal helpers that mutate LazyCallGraph state + ///{ + static LazyCallGraph::Node &createNode(LazyCallGraph &LCG, Function &Fn); + static void addNodeToSCC(LazyCallGraph &LCG, LazyCallGraph::SCC &SCC, + LazyCallGraph::Node &N); + ///} + public: CallGraphUpdater() {} ~CallGraphUpdater() { finalize(); } @@ -79,6 +86,11 @@ /// still needs to be re-analyzed or manually updated. void registerOutlinedFunction(Function &NewFn); + /// 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); + /// Replace \p OldFn in the call graph (and SCC) with \p NewFn. The uses /// outside the call graph and the function \p OldFn are not modified. void replaceFunctionWith(Function &OldFn, Function &NewFn); 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 @@ -18,6 +18,23 @@ using namespace llvm; +LazyCallGraph::Node &CallGraphUpdater::createNode(LazyCallGraph &LCG, + Function &Fn) { + 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 &LCG, LazyCallGraph::SCC &SCC, + LazyCallGraph::Node &N) { + SCC.Nodes.push_back(&N); + LCG.SCCMap[&N] = &SCC; +} + bool CallGraphUpdater::finalize() { if (!DeadFunctionsInComdats.empty()) { filterDeadComdatFunctions(*DeadFunctionsInComdats.front()->getParent(), @@ -87,12 +104,26 @@ 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(*LCG, NewFn); + addNodeToSCC(*LCG, *SCC, CGNode); + } +} + +void CallGraphUpdater::registerReferredToOutlinedFunction(Function &NewFn) { + if (CG) { + llvm_unreachable("registering referred to functions is not supported " + "with CallGraph"); + } else if (LCG) { + LazyCallGraph::Node &CGNode = createNode(*LCG, NewFn); + + LazyCallGraph::RefSCC &RefSCC = SCC->getOuterRefSCC(); + SmallVector Nodes; + Nodes.push_back(&CGNode); + auto *NewSCC = LCG->createSCC(RefSCC, Nodes); + addNodeToSCC(*LCG, *NewSCC, CGNode); + + RefSCC.SCCIndices[NewSCC] = RefSCC.SCCIndices.size(); + RefSCC.SCCs.push_back(NewSCC); } } 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 @@ -1665,5 +1665,55 @@ 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; + CGUpdater.initialize(CG, C, 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); + + // "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