diff --git a/include/llvm/Analysis/LazyCallGraph.h b/include/llvm/Analysis/LazyCallGraph.h index aeeeb9a966a..a025f2275fb 100644 --- a/include/llvm/Analysis/LazyCallGraph.h +++ b/include/llvm/Analysis/LazyCallGraph.h @@ -971,7 +971,17 @@ public: /// /// These functions, because they are known to LLVM, can have calls /// introduced out of thin air from arbitrary IR. - ArrayRef getLibFunctions() const { return LibFunctions; } + ArrayRef getLibFunctions() const { + return LibFunctions.getArrayRef(); + } + + /// Test whether a function is a known and defined library function tracked by + /// the call graph. + /// + /// Because these functions are known to LLVM they are specially modeled in + /// the call graph and even when all IR-level references have been removed + /// remain active and reachable. + bool isLibFunction(Function &F) const { return LibFunctions.count(&F); } ///@{ /// \name Pre-SCC Mutation API @@ -1110,7 +1120,7 @@ private: /// Defined functions that are also known library functions which the /// optimizer can reason about and therefore might introduce calls to out of /// thin air. - SmallVector LibFunctions; + SmallSetVector LibFunctions; /// Helper to insert a new function, with an already looked-up entry in /// the NodeMap. diff --git a/lib/Analysis/LazyCallGraph.cpp b/lib/Analysis/LazyCallGraph.cpp index 5e14c0df6d0..d287f81985f 100644 --- a/lib/Analysis/LazyCallGraph.cpp +++ b/lib/Analysis/LazyCallGraph.cpp @@ -144,7 +144,7 @@ LazyCallGraph::LazyCallGraph(Module &M, TargetLibraryInfo &TLI) { // synthesize reference edges to it to model the fact that LLVM can turn // arbitrary code into a library function call. if (isKnownLibFunction(F, TLI)) - LibFunctions.push_back(&F); + LibFunctions.insert(&F); if (F.hasLocalLinkage()) continue; @@ -1608,6 +1608,11 @@ void LazyCallGraph::removeDeadFunction(Function &F) { assert(F.use_empty() && "This routine should only be called on trivially dead functions!"); + // We shouldn't remove library functions as they are never really dead while + // the call graph is in use -- every function definition refers to them. + assert(!isLibFunction(F) && + "Must not remove lib functions from the call graph!"); + auto NI = NodeMap.find(&F); if (NI == NodeMap.end()) // Not in the graph at all! diff --git a/lib/Transforms/IPO/Inliner.cpp b/lib/Transforms/IPO/Inliner.cpp index 00ddb93df83..317770d133b 100644 --- a/lib/Transforms/IPO/Inliner.cpp +++ b/lib/Transforms/IPO/Inliner.cpp @@ -909,7 +909,7 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC, // 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()) { + if (Callee.use_empty() && !CG.isLibFunction(Callee)) { Calls.erase( std::remove_if(Calls.begin() + i + 1, Calls.end(), [&Callee](const std::pair &Call) { diff --git a/test/Other/cgscc-libcall-update.ll b/test/Other/cgscc-libcall-update.ll index 53fe24fc38a..e0833ca0926 100644 --- a/test/Other/cgscc-libcall-update.ll +++ b/test/Other/cgscc-libcall-update.ll @@ -1,10 +1,12 @@ ; Make sure that the CGSCC pass manager can handle when instcombine simplifies ; one libcall into an unrelated libcall and update the call graph accordingly. ; -; RUN: opt -passes='cgscc(function(instcombine))' -S < %s | FileCheck %s +; Also check that it can handle inlining *removing* a libcall entirely. +; +; RUN: opt -passes='cgscc(inline,function(instcombine))' -S < %s | FileCheck %s define i8* @wibble(i8* %arg1, i8* %arg2) { -; CHECK-LABLE: define @wibble( +; CHECK-LABEL: define i8* @wibble( bb: %tmp = alloca [1024 x i8], align 16 %tmp2 = getelementptr inbounds [1024 x i8], [1024 x i8]* %tmp, i64 0, i64 0 @@ -20,7 +22,7 @@ bb: ; CHECK: ret } -define i8* @strncpy(i8* %arg1, i8* %arg2, i64 %size) { +define i8* @strncpy(i8* %arg1, i8* %arg2, i64 %size) noinline { bb: %result = call i8* @my_special_strncpy(i8* %arg1, i8* %arg2, i64 %size) ret i8* %result @@ -33,3 +35,27 @@ declare i64 @llvm.objectsize.i64.p0i8(i8*, i1, i1) declare i8* @__strncpy_chk(i8*, i8*, i64, i64) declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) + +; Check that even when we completely remove a libcall we don't get the call +; graph wrong once we handle libcalls in the call graph specially to address +; the above case. +define i32 @hoge(i32* %arg1) { +; CHECK-LABEL: define i32 @hoge( +bb: + %tmp41 = load i32*, i32** null + %tmp6 = load i32, i32* %arg1 + %tmp7 = call i32 @ntohl(i32 %tmp6) +; CHECK-NOT: call i32 @ntohl + ret i32 %tmp7 +; CHECK: ret i32 +} + +; Even though this function is not used, it should be retained as it may be +; used when doing further libcall transformations. +define internal i32 @ntohl(i32 %x) { +; CHECK-LABEL: define internal i32 @ntohl( +entry: + %and2 = lshr i32 %x, 8 + %shr = and i32 %and2, 65280 + ret i32 %shr +}