[llvm] 6ad7027 - [MachineOutliner] Preserve debug locations on outlined call sequences (#224189)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 14:04:28 PDT 2026


Author: Kyungwoo Lee
Date: 2026-09-22T21:04:21Z
New Revision: 6ad70274f35f15ff2ac8c2f2b1425a52b3bfcc48

URL: https://github.com/llvm/llvm-project/commit/6ad70274f35f15ff2ac8c2f2b1425a52b3bfcc48
DIFF: https://github.com/llvm/llvm-project/commit/6ad70274f35f15ff2ac8c2f2b1425a52b3bfcc48.diff

LOG: [MachineOutliner] Preserve debug locations on outlined call sequences (#224189)

Machine Outliner replacement call sequences currently have no DebugLoc.
When one immediately follows another call, its address can be emitted
with DWARF line 0, meaning “unknown source location,” so symbolication
finds the function but not the source line.

Copy the first usable source location from each candidate to its
replacement call sequence, including any target-inserted save and
restore instructions. If none exists, leave the sequence locationless.
This matches the IR Outliner’s policy: preserve each replacement call’s
location while leaving the shared outlined body locationless because it
represents multiple source regions.

Added: 
    llvm/test/CodeGen/AArch64/machine-outliner-call-debugloc.mir
    llvm/test/DebugInfo/AArch64/machine-outliner-call-debugloc.ll

Modified: 
    llvm/lib/CodeGen/MachineOutliner.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/lib/CodeGen/MachineOutliner.cpp b/llvm/lib/CodeGen/MachineOutliner.cpp
index 6716dc8d144670..171705f1d68d55 100644
--- a/llvm/lib/CodeGen/MachineOutliner.cpp
+++ b/llvm/lib/CodeGen/MachineOutliner.cpp
@@ -1109,9 +1109,35 @@ bool MachineOutliner::outline(
       MachineBasicBlock::iterator StartIt = C.begin();
       MachineBasicBlock::iterator EndIt = std::prev(C.end());
 
+      // Use the first non-debug instruction with a non-zero source line as the
+      // location for the replacement call sequence.
+      DebugLoc CallLoc;
+      for (MachineInstr &MI : C) {
+        const DebugLoc &DL = MI.getDebugLoc();
+        if (!MI.isDebugInstr() && DL && DL.getLine()) {
+          CallLoc = DL;
+          break;
+        }
+      }
+
+      // Remember the instruction the call sequence will be inserted after, so
+      // we can find every instruction the target inserts below.
+      MachineBasicBlock::iterator PrevIt =
+          StartIt == MBB.begin() ? MBB.end() : std::prev(StartIt);
+
       // Insert the call.
       auto CallInst = TII.insertOutlinedCall(M, MBB, StartIt, *MF, C);
-// Insert the call.
+
+      // insertOutlinedCall may emit link register save/restore instructions
+      // around the call, and leaves StartIt on the last instruction it
+      // inserted. Give the whole sequence the candidate's location. Otherwise,
+      // a locationless save or restore can introduce a line 0 row, including
+      // at the return address immediately after the call.
+      MachineBasicBlock::iterator SeqBegin =
+          PrevIt == MBB.end() ? MBB.begin() : std::next(PrevIt);
+      for (MachineInstr &MI : make_range(SeqBegin, std::next(StartIt)))
+        MI.setDebugLoc(CallLoc);
+
 #ifndef NDEBUG
       auto MBBBeingOutlinedFromName =
           MBB.getName().empty() ? "<unknown>" : MBB.getName().str();

diff  --git a/llvm/test/CodeGen/AArch64/machine-outliner-call-debugloc.mir b/llvm/test/CodeGen/AArch64/machine-outliner-call-debugloc.mir
new file mode 100644
index 00000000000000..b5dca26cf273f3
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/machine-outliner-call-debugloc.mir
@@ -0,0 +1,204 @@
+# RUN: llc -mtriple=aarch64-apple-darwin -run-pass=prolog-epilog \
+# RUN:   -run-pass=machine-outliner -verify-machineinstrs %s -o - | FileCheck %s
+
+# Each call replacing an outlined sequence takes the first source location from
+# the sequence it replaces. Any link register save/restore the target inserts
+# around that call takes the same location, so that the call site does not
+# begin with a line 0 address.
+
+# The link register is dead here, so the call needs no save/restore.
+# CHECK-LABEL: name: caller
+# CHECK:       BL @callee0
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 10,
+# CHECK:       BL @callee1
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 20,
+# CHECK:       BL @callee2
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 30,
+# CHECK:       BL @callee3
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 40,
+# CHECK:       BL @callee4
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}
+# CHECK-SAME:  implicit $sp{{$}}
+
+# The link register is live, but a callee-saved register is free, so it is
+# saved to that register around the call.
+# CHECK-LABEL: name: reg_save
+# CHECK:       $x[[REG:[0-9]+]] = ORRXrs $xzr, $lr, 0, debug-location !DILocation(line: 11,
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 11,
+# CHECK-NEXT:  $lr = ORRXrs $xzr, $x[[REG]], 0, debug-location !DILocation(line: 11,
+# CHECK:       $x[[REG]] = ORRXrs $xzr, $lr, 0, debug-location !DILocation(line: 22,
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 22,
+# CHECK-NEXT:  $lr = ORRXrs $xzr, $x[[REG]], 0, debug-location !DILocation(line: 22,
+
+# No register is free, so the link register is saved to the stack instead.
+# CHECK-LABEL: name: stack_save
+# CHECK:       early-clobber $sp = STRXpre $lr, $sp, -16, debug-location !DILocation(line: 111,
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 111,
+# CHECK-NEXT:  early-clobber $sp, $lr = LDRXpost $sp, 16, debug-location !DILocation(line: 111,
+# CHECK:       early-clobber $sp = STRXpre $lr, $sp, -16, debug-location !DILocation(line: 222,
+# CHECK-NEXT:  BL @OUTLINED_FUNCTION_{{[0-9]+}}{{.*}}debug-location !DILocation(line: 222,
+# CHECK-NEXT:  early-clobber $sp, $lr = LDRXpost $sp, 16, debug-location !DILocation(line: 222,
+
+--- |
+  target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
+  target triple = "aarch64--"
+
+  declare void @callee0()
+  declare void @callee1()
+  declare void @callee2()
+  declare void @callee3()
+  declare void @callee4()
+
+  define void @caller() #0 !dbg !6 {
+    ret void
+  }
+
+  define void @reg_save() #0 !dbg !20 {
+    ret void
+  }
+
+  define void @stack_save() #0 !dbg !30 {
+    ret void
+  }
+
+  attributes #0 = { minsize noredzone nounwind }
+
+  !llvm.dbg.cu = !{!0}
+  !llvm.module.flags = !{!3, !4}
+
+  !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+  !1 = !DIFile(filename: "test.c", directory: "/tmp")
+  !2 = !{}
+  !3 = !{i32 7, !"Dwarf Version", i32 4}
+  !4 = !{i32 2, !"Debug Info Version", i32 3}
+  !5 = !DISubroutineType(types: !2)
+  !6 = distinct !DISubprogram(name: "caller", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+  !7 = !DILocation(line: 9, column: 1, scope: !6)
+  !8 = !DILocation(line: 10, column: 1, scope: !6)
+  !9 = !DILocation(line: 19, column: 1, scope: !6)
+  !10 = !DILocation(line: 20, column: 1, scope: !6)
+  !11 = !DILocation(line: 29, column: 1, scope: !6)
+  !12 = !DILocation(line: 30, column: 1, scope: !6)
+  !13 = !DILocation(line: 39, column: 1, scope: !6)
+  !14 = !DILocation(line: 40, column: 1, scope: !6)
+  !20 = distinct !DISubprogram(name: "reg_save", scope: !1, file: !1, line: 10, type: !5, scopeLine: 10, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+  !21 = !DILocation(line: 11, column: 1, scope: !20)
+  !22 = !DILocation(line: 22, column: 1, scope: !20)
+  !23 = !DILocation(line: 33, column: 1, scope: !20)
+  !24 = !DILocation(line: 44, column: 1, scope: !20)
+  !30 = distinct !DISubprogram(name: "stack_save", scope: !1, file: !1, line: 100, type: !5, scopeLine: 100, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+  !31 = !DILocation(line: 111, column: 1, scope: !30)
+  !32 = !DILocation(line: 222, column: 1, scope: !30)
+  !33 = !DILocation(line: 333, column: 1, scope: !30)
+
+...
+---
+name:            caller
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $lr
+
+    BL @callee0, implicit-def dead $lr, implicit $sp, debug-location !7
+    $w0 = ORRWri $wzr, 1, debug-location !8
+    $w1 = ORRWri $wzr, 2, debug-location !8
+    $w2 = ORRWri $wzr, 3
+
+    BL @callee1, implicit-def dead $lr, implicit $sp, debug-location !9
+    $w0 = ORRWri $wzr, 1, debug-location !10
+    $w1 = ORRWri $wzr, 2, debug-location !10
+    $w2 = ORRWri $wzr, 4
+
+    BL @callee2, implicit-def dead $lr, implicit $sp, debug-location !11
+    $w0 = ORRWri $wzr, 1, debug-location !12
+    $w1 = ORRWri $wzr, 2, debug-location !12
+    $w2 = ORRWri $wzr, 5
+
+    BL @callee3, implicit-def dead $lr, implicit $sp, debug-location !13
+    $w0 = ORRWri $wzr, 1, debug-location !14
+    $w1 = ORRWri $wzr, 2, debug-location !14
+    $w2 = ORRWri $wzr, 6
+
+    BL @callee4, implicit-def dead $lr, implicit $sp
+    $w0 = ORRWri $wzr, 1
+    $w1 = ORRWri $wzr, 2
+    $w2 = ORRWri $wzr, 7
+
+    RET undef $lr
+...
+---
+name:            reg_save
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    $x9 = ORRXri $xzr, 1
+    $lr = ORRXri $xzr, 1
+  bb.1:
+    liveins: $lr, $w9
+    $w9 = ORRWri $wzr, 1, debug-location !21
+    $w9 = ORRWri $wzr, 1, debug-location !21
+    $w9 = ORRWri $wzr, 1, debug-location !21
+    $w9 = ORRWri $wzr, 1, debug-location !21
+    $w9 = ORRWri $wzr, 1, debug-location !21
+    $w9 = ORRWri $wzr, 2, debug-location !21
+  bb.2:
+    liveins: $lr, $w9
+    $w9 = ORRWri $wzr, 1, debug-location !22
+    $w9 = ORRWri $wzr, 1, debug-location !22
+    $w9 = ORRWri $wzr, 1, debug-location !22
+    $w9 = ORRWri $wzr, 1, debug-location !22
+    $w9 = ORRWri $wzr, 1, debug-location !22
+    $w9 = ORRWri $wzr, 2, debug-location !22
+  bb.3:
+    liveins: $lr, $w9
+    $w9 = ORRWri $wzr, 1, debug-location !23
+    $w9 = ORRWri $wzr, 1, debug-location !23
+    $w9 = ORRWri $wzr, 1, debug-location !23
+    $w9 = ORRWri $wzr, 1, debug-location !23
+    $w9 = ORRWri $wzr, 1, debug-location !23
+    $w9 = ORRWri $wzr, 2, debug-location !23
+  bb.4:
+    liveins: $lr, $w9
+    $w9 = ORRWri $wzr, 1, debug-location !24
+    $w9 = ORRWri $wzr, 1, debug-location !24
+    $w9 = ORRWri $wzr, 1, debug-location !24
+    $w9 = ORRWri $wzr, 1, debug-location !24
+    $w9 = ORRWri $wzr, 1, debug-location !24
+    $w9 = ORRWri $wzr, 2, debug-location !24
+  bb.5:
+    liveins: $w9
+    RET undef $lr
+...
+---
+name:            stack_save
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $lr, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x19, $x20, $x21, $x22, $x23, $x24, $x25, $x26, $x27, $x28
+    $lr = ORRXri $xzr, 1
+    $w10 = ORRWri $wzr, 1, debug-location !31
+    $w10 = ORRWri $wzr, 1, debug-location !31
+    $w10 = ORRWri $wzr, 1, debug-location !31
+    $w10 = ORRWri $wzr, 1, debug-location !31
+    $w10 = ORRWri $wzr, 1, debug-location !31
+    $w12 = ORRWri $wzr, 2, debug-location !31
+  bb.1:
+    liveins: $lr, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x19, $x20, $x21, $x22, $x23, $x24, $x25, $x26, $x27, $x28
+    $w10 = ORRWri $wzr, 1, debug-location !32
+    $w10 = ORRWri $wzr, 1, debug-location !32
+    $w10 = ORRWri $wzr, 1, debug-location !32
+    $w10 = ORRWri $wzr, 1, debug-location !32
+    $w10 = ORRWri $wzr, 1, debug-location !32
+    $w12 = ORRWri $wzr, 2, debug-location !32
+  bb.2:
+    liveins: $lr, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x19, $x20, $x21, $x22, $x23, $x24, $x25, $x26, $x27, $x28
+    $w10 = ORRWri $wzr, 1, debug-location !33
+    $w10 = ORRWri $wzr, 1, debug-location !33
+    $w10 = ORRWri $wzr, 1, debug-location !33
+    $w10 = ORRWri $wzr, 1, debug-location !33
+    $w10 = ORRWri $wzr, 1, debug-location !33
+    $w12 = ORRWri $wzr, 2, debug-location !33
+  bb.3:
+    liveins: $lr, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x19, $x20, $x21, $x22, $x23, $x24, $x25, $x26, $x27, $x28
+    RET undef $lr
+...

diff  --git a/llvm/test/DebugInfo/AArch64/machine-outliner-call-debugloc.ll b/llvm/test/DebugInfo/AArch64/machine-outliner-call-debugloc.ll
new file mode 100644
index 00000000000000..a0b9e7f8ed3847
--- /dev/null
+++ b/llvm/test/DebugInfo/AArch64/machine-outliner-call-debugloc.ll
@@ -0,0 +1,84 @@
+; RUN: llc -mtriple=aarch64-linux-gnu -enable-machine-outliner -filetype=obj %s -o %t
+; RUN: llvm-objdump --disassemble --no-show-raw-insn %t > %t.dump
+; RUN: llvm-dwarfdump --debug-line %t >> %t.dump
+; RUN: FileCheck %s < %t.dump
+
+; Capture each outlined call sequence and the instruction after it. Check that
+; each sequence has one continuous source line and that the following
+; instruction returns to its own line. The shared outlined function is
+; artificial and legitimately has line 0.
+
+; CHECK-LABEL: <caller>:
+; CHECK:      [[#%x,FIRST_SAVE:]]:{{.*}}mov [[SAVE_REG:x[0-9]+]], x30
+; CHECK-NEXT: {{[0-9a-f]+}}:{{.*}}bl{{.*}}<OUTLINED_FUNCTION_0>
+; CHECK-NEXT: {{[0-9a-f]+}}:{{.*}}mov x30, [[SAVE_REG]]
+; CHECK-NEXT: [[#%x,FIRST_AFTER:]]:{{.*}}add
+; CHECK:      [[#%x,SECOND_SAVE:]]:{{.*}}mov [[SAVE_REG]], x30
+; CHECK-NEXT: {{[0-9a-f]+}}:{{.*}}bl{{.*}}<OUTLINED_FUNCTION_0>
+; CHECK-NEXT: {{[0-9a-f]+}}:{{.*}}mov x30, [[SAVE_REG]]
+; CHECK-NEXT: [[#%x,SECOND_AFTER:]]:{{.*}}subs
+; CHECK:      [[#%x,OUTLINED:]] <OUTLINED_FUNCTION_0>:
+
+; CHECK: Address            Line
+; CHECK:      0x[[#%.16x,FIRST_SAVE]]     10
+; CHECK-NEXT: 0x[[#%.16x,FIRST_AFTER]]     11
+; CHECK-NEXT: 0x[[#%.16x,SECOND_SAVE]]     20
+; CHECK-NEXT: 0x[[#%.16x,SECOND_AFTER]]     21
+; CHECK:      0x[[#%.16x,OUTLINED]]      0
+
+define void @caller() #0 !dbg !6 {
+  %slot1 = alloca i32, align 4
+  %slot2 = alloca i32, align 4
+  %slot3 = alloca i32, align 4
+  %slot4 = alloca i32, align 4
+  store i32 0, ptr %slot1, align 4, !dbg !7
+  store i32 0, ptr %slot2, align 4, !dbg !7
+  store i32 0, ptr %slot3, align 4, !dbg !7
+  store i32 0, ptr %slot4, align 4, !dbg !7
+
+  %v1 = load i32, ptr %slot1, align 4, !dbg !8
+  %v2 = add nsw i32 %v1, 1, !dbg !8
+  store i32 %v2, ptr %slot1, align 4, !dbg !8
+  %v3 = load i32, ptr %slot3, align 4, !dbg !8
+  %v4 = add nsw i32 %v3, 1, !dbg !8
+  store i32 %v4, ptr %slot3, align 4, !dbg !8
+  %v5 = load i32, ptr %slot4, align 4, !dbg !8
+  %v6 = add nsw i32 %v5, 1, !dbg !8
+  store i32 %v6, ptr %slot4, align 4, !dbg !8
+  %v7 = load i32, ptr %slot2, align 4, !dbg !8
+  %v8 = add nsw i32 %v7, 1, !dbg !9
+  store i32 %v8, ptr %slot2, align 4, !dbg !9
+
+  %v9 = load i32, ptr %slot1, align 4, !dbg !10
+  %v10 = add nsw i32 %v9, 1, !dbg !10
+  store i32 %v10, ptr %slot1, align 4, !dbg !10
+  %v11 = load i32, ptr %slot3, align 4, !dbg !10
+  %v12 = add nsw i32 %v11, 1, !dbg !10
+  store i32 %v12, ptr %slot3, align 4, !dbg !10
+  %v13 = load i32, ptr %slot4, align 4, !dbg !10
+  %v14 = add nsw i32 %v13, 1, !dbg !10
+  store i32 %v14, ptr %slot4, align 4, !dbg !10
+  %v15 = load i32, ptr %slot2, align 4, !dbg !10
+  %v16 = add nsw i32 %v15, -1, !dbg !11
+  store i32 %v16, ptr %slot2, align 4, !dbg !11
+  ret void, !dbg !12
+}
+
+attributes #0 = { noinline noredzone nounwind optnone }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !DISubroutineType(types: !2)
+!6 = distinct !DISubprogram(name: "caller", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!7 = !DILocation(line: 2, column: 1, scope: !6)
+!8 = !DILocation(line: 10, column: 1, scope: !6)
+!9 = !DILocation(line: 11, column: 1, scope: !6)
+!10 = !DILocation(line: 20, column: 1, scope: !6)
+!11 = !DILocation(line: 21, column: 1, scope: !6)
+!12 = !DILocation(line: 22, column: 1, scope: !6)


        


More information about the llvm-commits mailing list