[llvm] [MergeFunctions] Add debug locations to redirected calls (PR #225625)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 23 00:50:41 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Mian Miftah (mmiftahx)
<details>
<summary>Changes</summary>
A call to a function without debug info does not need a debug location, even in a function with debug info. If MergeFunctions redirects such a call to an equivalent function that has debug info, the verifier fails:
```
inlinable function call in a function with debug info must have a !dbg location
```
Before redirecting such a call, give it a line 0 location in the scope of the caller, as `DwarfEHPrepare` does for the call to the rewind function. This covers both ways calls are redirected: `replaceDirectCallers()`, and `replaceAllUsesWith()` for `unnamed_addr` functions.
---
Full diff: https://github.com/llvm/llvm-project/pull/225625.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/IPO/MergeFunctions.cpp (+18)
- (added) llvm/test/Transforms/MergeFunc/redirect-call-debug-loc.ll (+98)
``````````diff
diff --git a/llvm/lib/Transforms/IPO/MergeFunctions.cpp b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
index 7fc49a5fcc855..a122508d5fbad 100644
--- a/llvm/lib/Transforms/IPO/MergeFunctions.cpp
+++ b/llvm/lib/Transforms/IPO/MergeFunctions.cpp
@@ -526,8 +526,25 @@ MergeFunctions::runOnFunctions(ArrayRef<Function *> Funcs) {
return this->DelToNewMap;
}
+// Direct calls to Old are about to call New. The verifier requires a location
+// on a call from a function with debug info to a function with debug info, but
+// a call to Old need not have one if Old has no debug info. Give such calls a
+// line 0 location in the caller.
+static void addMissingCallLocations(Function *Old, Function *New) {
+ if (!New->getSubprogram())
+ return;
+ for (Use &U : Old->uses()) {
+ auto *CB = dyn_cast<CallBase>(U.getUser());
+ if (!CB || !CB->isCallee(&U) || CB->getDebugLoc())
+ continue;
+ if (DISubprogram *SP = CB->getFunction()->getSubprogram())
+ CB->setDebugLoc(DILocation::get(CB->getContext(), 0, 0, SP));
+ }
+}
+
// Replace direct callers of Old with New.
void MergeFunctions::replaceDirectCallers(Function *Old, Function *New) {
+ addMissingCallLocations(Old, New);
for (Use &U : make_early_inc_range(Old->uses())) {
CallBase *CB = dyn_cast<CallBase>(U.getUser());
if (CB && CB->isCallee(&U)) {
@@ -1200,6 +1217,7 @@ void MergeFunctions::mergeTwoFunctions(Function *F, Function *G) {
GlobalNumbers.erase(G);
// If G's address is not significant, replace it entirely.
removeUsers(G);
+ addMissingCallLocations(G, F);
G->replaceAllUsesWith(F);
} else {
// Redirect direct callers of G to F. (See note on MergeFunctionsPDI
diff --git a/llvm/test/Transforms/MergeFunc/redirect-call-debug-loc.ll b/llvm/test/Transforms/MergeFunc/redirect-call-debug-loc.ll
new file mode 100644
index 0000000000000..c6493cfe1ddae
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/redirect-call-debug-loc.ll
@@ -0,0 +1,98 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 6
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+; A call to a function without debug info need not have a location. If it is
+; redirected to an equal function with debug info, and the caller has debug
+; info, it needs one.
+
+define i32 @f(ptr %p) !dbg !5 {
+; CHECK-LABEL: define i32 @f(
+; CHECK-SAME: ptr [[P:%.*]]) !dbg [[DBG3:![0-9]+]] {
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4, !dbg [[DBG6:![0-9]+]]
+; CHECK-NEXT: ret i32 [[V]], !dbg [[DBG6]]
+;
+ %v = load i32, ptr %p, !dbg !6
+ ret i32 %v, !dbg !6
+}
+
+; The direct callers of @g are redirected to @f.
+define internal i32 @g(ptr %p) {
+ %v = load i32, ptr %p
+ ret i32 %v
+}
+
+; The address of @g_unnamed_addr is not significant, so all its uses are
+; replaced with @f.
+define internal i32 @g_unnamed_addr(ptr %p) unnamed_addr {
+ %v = load i32, ptr %p
+ ret i32 %v
+}
+
+; The shared body of the linkonce_odr functions @f_odr and @g_odr becomes a
+; private function, and the direct callers of @g_odr are redirected to it.
+define linkonce_odr i32 @f_odr(ptr %p) !dbg !9 {
+ %v = load i32, ptr %p, !dbg !10
+ %w = add i32 %v, 1, !dbg !10
+ ret i32 %w, !dbg !10
+}
+
+define linkonce_odr i32 @g_odr(ptr %p) {
+ %v = load i32, ptr %p
+ %w = add i32 %v, 1
+ ret i32 %w
+}
+
+define void @caller(ptr %p) !dbg !7 {
+; CHECK-LABEL: define void @caller(
+; CHECK-SAME: ptr [[P:%.*]]) !dbg [[DBG9:![0-9]+]] {
+; CHECK-NEXT: [[TMP1:%.*]] = call i32 @f(ptr [[P]]), !dbg [[DBG10:![0-9]+]]
+; CHECK-NEXT: [[TMP2:%.*]] = call i32 @f(ptr [[P]]), !dbg [[DBG11:![0-9]+]]
+; CHECK-NEXT: [[TMP3:%.*]] = call i32 @f(ptr [[P]]), !dbg [[DBG10]]
+; CHECK-NEXT: [[TMP4:%.*]] = call i32 @[[GLOB0:[0-9]+]](ptr [[P]]), !dbg [[DBG10]]
+; CHECK-NEXT: ret void, !dbg [[DBG11]]
+;
+ call i32 @g(ptr %p)
+ call i32 @g(ptr %p), !dbg !8
+ call i32 @g_unnamed_addr(ptr %p)
+ call i32 @g_odr(ptr %p)
+ ret void, !dbg !8
+}
+
+define void @caller_without_debug_info(ptr %p) {
+; CHECK-LABEL: define void @caller_without_debug_info(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i32 @f(ptr [[P]])
+; CHECK-NEXT: ret void
+;
+ call i32 @g(ptr %p)
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.c", directory: "")
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = !{}
+!4 = !DISubroutineType(types: !3)
+!5 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!6 = !DILocation(line: 1, column: 1, scope: !5)
+!7 = distinct !DISubprogram(name: "caller", scope: !1, file: !1, line: 3, type: !4, scopeLine: 3, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!8 = !DILocation(line: 4, column: 3, scope: !7)
+!9 = distinct !DISubprogram(name: "f_odr", scope: !1, file: !1, line: 6, type: !4, scopeLine: 6, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!10 = !DILocation(line: 6, column: 1, scope: !9)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}test.c", directory: {{.*}})
+; CHECK: [[META2:![0-9]+]] = !{i32 2, !"Debug Info Version", i32 3}
+; CHECK: [[DBG3]] = distinct !DISubprogram(name: "f", scope: [[META1]], file: [[META1]], line: 1, type: [[META4:![0-9]+]], scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]])
+; CHECK: [[META4]] = !DISubroutineType(types: [[META5:![0-9]+]])
+; CHECK: [[META5]] = !{}
+; CHECK: [[DBG6]] = !DILocation(line: 1, column: 1, scope: [[DBG3]])
+; CHECK: [[META7:![0-9]+]] = distinct !DISubprogram(name: "f_odr", scope: [[META1]], file: [[META1]], line: 6, type: [[META4]], scopeLine: 6, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]])
+; CHECK: [[META8:![0-9]+]] = !DILocation(line: 6, column: 1, scope: [[META7]])
+; CHECK: [[DBG9]] = distinct !DISubprogram(name: "caller", scope: [[META1]], file: [[META1]], line: 3, type: [[META4]], scopeLine: 3, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]])
+; CHECK: [[DBG10]] = !DILocation(line: 0, scope: [[DBG9]])
+; CHECK: [[DBG11]] = !DILocation(line: 4, column: 3, scope: [[DBG9]])
+;.
``````````
</details>
https://github.com/llvm/llvm-project/pull/225625
More information about the llvm-commits
mailing list