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,25 +939,30 @@ // 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() && Callee.hasZeroLiveUses() && + if (Callee.isDiscardableIfUnused() && Callee.hasZeroLiveUses() && !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) Advice->recordInliningWithCalleeDeleted(); @@ -1015,6 +1024,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-function-with-metadata-use.ll b/llvm/test/Transforms/Inline/delete-function-with-metadata-use.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/delete-function-with-metadata-use.ll @@ -0,0 +1,39 @@ +; RUN: opt -passes=inline < %s -S | FileCheck %s + +; CHECK: define {{.*}}@f1 +; CHECK-NOT: define + +%a = type { i8*, i8* } + +$f3 = comdat any + +define linkonce_odr void @f1() { + call void @f2(void ()* @f3) + ret void +} + +define linkonce_odr void @f2(void ()* %__f) { + call void @llvm.dbg.value(metadata void ()* %__f, metadata !2, metadata !DIExpression()), !dbg !10 + call void %__f() + ret void +} + +define linkonce_odr void @f3() comdat { + ret void +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.module.flags = !{!0, !1} + +!0 = !{i32 7, !"Dwarf Version", i32 4} +!1 = !{i32 2, !"Debug Info Version", i32 3} +!2 = !DILocalVariable(name: "__f", arg: 3, scope: !3, file: !4, line: 3814, type: !9) +!3 = distinct !DISubprogram(scope: !5, file: !4, line: 3814, type: !6, scopeLine: 3815, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !8, templateParams: !7, retainedNodes: !7) +!4 = !DIFile(filename: "a", directory: "") +!5 = !DINamespace(name: "std", scope: null) +!6 = !DISubroutineType(types: !7) +!7 = !{} +!8 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !4, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !7, retainedTypes: !7, globals: !7, imports: !7, splitDebugInlining: false, nameTableKind: None) +!9 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64) +!10 = !DILocation(line: 0, scope: !3) 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 +}