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 @@ -823,6 +823,10 @@ // defer deleting these to make it easier to handle the call graph updates. SmallVector DeadFunctions; + // Track potentially dead non-local functions with comdats to see if they can + // be deleted as a batch after inlining. + SmallVector DeadFunctionsInComdats; + // Loop forward over all of the calls. while (!Calls->empty()) { // We expect the calls to typically be batched with sequences of calls that @@ -935,28 +939,33 @@ // Merge the attributes based on the inlining. AttributeFuncs::mergeAttributesForInlining(F, Callee); - // 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. + // For local functions or discardable functions without comdats, 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. Non-local + // discardable functions with comdats are checked later on. bool CalleeWasDeleted = false; - if (Callee.hasLocalLinkage()) { + if (Callee.isDiscardableIfUnused()) { // 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() && !CG.isLibFunction(Callee)) { - Calls->erase_if([&](const std::pair &Call) { - return Call.first->getCaller() == &Callee; - }); - // Clear the body and queue the function itself for deletion when we - // finish inlining and call graph updates. - // Note that after this point, it is an error to do anything other - // than use the callee's address or delete it. - Callee.dropAllReferences(); - assert(!is_contained(DeadFunctions, &Callee) && - "Cannot put cause a function to become dead twice!"); - DeadFunctions.push_back(&Callee); - CalleeWasDeleted = true; + if (Callee.hasLocalLinkage() || !Callee.hasComdat()) { + Calls->erase_if([&](const std::pair &Call) { + return Call.first->getCaller() == &Callee; + }); + // Clear the body and queue the function itself for deletion when we + // finish inlining and call graph updates. + // Note that after this point, it is an error to do anything other + // than use the callee's address or delete it. + Callee.dropAllReferences(); + assert(!is_contained(DeadFunctions, &Callee) && + "Cannot put cause a function to become dead twice!"); + DeadFunctions.push_back(&Callee); + CalleeWasDeleted = true; + } else { + DeadFunctionsInComdats.push_back(&Callee); + } } } if (CalleeWasDeleted) @@ -1019,6 +1028,15 @@ FAM.invalidate(F, PreservedAnalyses::none()); } + // We must ensure that we only delete functions with comdats if every function + // in the comdat is going to be deleted. + if (!DeadFunctionsInComdats.empty()) { + filterDeadComdatFunctions(DeadFunctionsInComdats); + for (auto *Callee : DeadFunctionsInComdats) + Callee->dropAllReferences(); + DeadFunctions.append(DeadFunctionsInComdats); + } + // 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. diff --git a/llvm/test/Transforms/Inline/delete-unused-function.ll b/llvm/test/Transforms/Inline/delete-unused-function.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/delete-unused-function.ll @@ -0,0 +1,63 @@ +; RUN: opt -passes=inline < %s -S | FileCheck %s + +; CHECK: define {{.*}}@caller +; CHECK: define {{.*}}@f1 +; CHECK-NOT: define {{.*}}@f2 +; CHECK-NOT: define {{.*}}@f3 +; CHECK-NOT: define {{.*}}@f4 +; CHECK-NOT: define {{.*}}@f5 +; CHECK: define {{.*}}@f6 +; CHECK-NOT: define {{.*}}@f7 +; CHECK-NOT: define {{.*}}@f8 + +$c1 = comdat any +$c2 = comdat any +$c3 = comdat any + +define void @caller() { + call void @f1() + call void @f2() + call void @f3() + call void @f4() + call void @f5() + call void @f6() + call void @f7() + call void @f8() + ret void +} + +define void @f1() { + ret void +} + +define internal void @f2() { + ret void +} + +define private void @f3() { + ret void +} + +define linkonce_odr void @f4() { + ret void +} + +define linkonce_odr void @f5() comdat($c1) { + ret void +} + +define linkonce_odr void @f6() comdat($c2) { + ret void +} + +define linkonce_odr void @g() comdat($c2) { + ret void +} + +define linkonce_odr void @f7() comdat($c3) { + ret void +} + +define linkonce_odr void @f8() comdat($c3) { + ret void +}