Skip to content

Conversation

@OCHyams
Copy link
Contributor

@OCHyams OCHyams commented Dec 13, 2025

Without this patch DW_AT_call_target is used for all indirect call address location expressions. The DWARF spec says:

For indirect calls or jumps where the address is not computable without use
of registers or memory locations that might be clobbered by the call the
DW_AT_call_target_clobbered attribute is used instead of the
DW_AT_call_target attribute.

This patch implements that behaviour.

…atile regs

Without this patch DW_AT_call_target is used for all indirect call address
location expressions. The DWARF spec says:

    For indirect calls or jumps where the address is not computable without use
    of registers or memory locations that might be clobbered by the call the
    DW_AT_call_target_clobbered attribute is used instead of the
    DW_AT_call_target attribute.

This patch implements that behaviour.
@llvmbot
Copy link
Member

llvmbot commented Dec 13, 2025

@llvm/pr-subscribers-debuginfo

Author: Orlando Cazalet-Hyams (OCHyams)

Changes

Without this patch DW_AT_call_target is used for all indirect call address location expressions. The DWARF spec says:

For indirect calls or jumps where the address is not computable without use
of registers or memory locations that might be clobbered by the call the
DW_AT_call_target_clobbered attribute is used instead of the
DW_AT_call_target attribute.

This patch implements that behaviour.


Full diff: https://github.com/llvm/llvm-project/pull/172167.diff

4 Files Affected:

  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp (+12-5)
  • (added) llvm/test/DebugInfo/X86/dwarf-call-target-clobbered.mir (+96)
  • (modified) llvm/test/DebugInfo/X86/dwarf-call-target-mem-loc.mir (+3-2)
  • (modified) llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs-indirect.ll (+2-2)
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
index d19de7f8000ec..c0421e628c7e9 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
@@ -1328,15 +1328,22 @@ DIE &DwarfCompileUnit::constructCallSiteEntryDIE(
 
   // A valid register in CallTarget indicates an indirect call.
   if (CallTarget.getReg()) {
+    // Add a DW_AT_call_target location expression describing the location of
+    // the address of the target function. If any register in the expression
+    // (i.e., the single register we currently handle) is volatile we must use
+    // DW_AT_call_target_clobbered instead.
+    const TargetRegisterInfo &TRI = *Asm->MF->getSubtarget().getRegisterInfo();
+    dwarf::Attribute Attribute = getDwarf5OrGNUAttr(
+        TRI.isCalleeSavedPhysReg(CallTarget.getReg(), *Asm->MF)
+            ? dwarf::DW_AT_call_target
+            : dwarf::DW_AT_call_target_clobbered);
+
     // CallTarget is the location of the address of an indirect call. The
     // location may be indirect, modified by Offset.
     if (CallTarget.isIndirect())
-      addMemoryLocation(CallSiteDIE,
-                        getDwarf5OrGNUAttr(dwarf::DW_AT_call_target),
-                        CallTarget, Offset);
+      addMemoryLocation(CallSiteDIE, Attribute, CallTarget, Offset);
     else
-      addAddress(CallSiteDIE, getDwarf5OrGNUAttr(dwarf::DW_AT_call_target),
-                 CallTarget);
+      addAddress(CallSiteDIE, Attribute, CallTarget);
   } else if (CalleeSP) {
     DIE *CalleeDIE = getOrCreateSubprogramDIE(CalleeSP, CalleeF);
     assert(CalleeDIE && "Could not create DIE for call site entry origin");
diff --git a/llvm/test/DebugInfo/X86/dwarf-call-target-clobbered.mir b/llvm/test/DebugInfo/X86/dwarf-call-target-clobbered.mir
new file mode 100644
index 0000000000000..ff5c78d7ad1cf
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/dwarf-call-target-clobbered.mir
@@ -0,0 +1,96 @@
+# RUN: llc %s --start-after=livedebugvalues -o - --filetype=obj | llvm-dwarfdump -  | FileCheck %s
+
+## Check that DW_AT_call_target_clobbered is used for a location expression
+## using a volatile register, otherwise DW_AT_call_target is used.
+
+## Generated from this C++ with llc -stop-after=livedebugvalues -simplify-mir:
+## __attribute__((disable_tail_calls)) void call_mem(void (**f)()) {
+##   (*f)();
+##   (*f)();
+## }
+
+## Which disassembles to -
+## 0000000000000000 <_Z8call_memPPFvvE>:
+##        0: 53                            pushq   %rbx
+##        1: 48 89 fb                      movq    %rdi, %rbx
+##        4: ff 17                         callq   *(%rdi)
+##        6: ff 13                         callq   *(%rbx)
+##        8: 5b                            popq    %rbx
+##        9: c3                            retq
+
+# CHECK: DW_TAG_call_site
+# CHECK-NEXT: DW_AT_call_target_clobbered (DW_OP_breg5 RDI+0)
+# CHECK: DW_TAG_call_site
+# CHECK-NEXT: DW_AT_call_target (DW_OP_breg3 RBX+0)
+
+--- |
+  target triple = "x86_64-unknown-linux-gnu"
+
+  define dso_local void @_Z8call_memPPFvvE(ptr noundef readonly captures(none) %f) local_unnamed_addr !dbg !5 {
+  entry:
+    %0 = load ptr, ptr %f, align 8, !dbg !13
+    call void %0(), !dbg !13
+    %1 = load ptr, ptr %f, align 8, !dbg !14
+    call void %1(), !dbg !14
+    ret void
+  }
+
+  !llvm.dbg.cu = !{!0}
+  !llvm.module.flags = !{!2, !3}
+  !llvm.ident = !{!4}
+
+  !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 22.0.0git", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+  !1 = !DIFile(filename: "test.cpp", directory: "/")
+  !2 = !{i32 7, !"Dwarf Version", i32 5}
+  !3 = !{i32 2, !"Debug Info Version", i32 3}
+  !4 = !{!"clang version 22.0.0git"}
+  !5 = distinct !DISubprogram(name: "call_mem", linkageName: "_Z8call_memPPFvvE", scope: !1, file: !1, line: 1, type: !6, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !12)
+  !6 = !DISubroutineType(types: !7)
+  !7 = !{null, !8}
+  !8 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !9, size: 64)
+  !9 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64)
+  !10 = !DISubroutineType(types: !11)
+  !11 = !{null}
+  !12 = !{}
+  !13 = !DILocation(line: 2, scope: !5)
+  !14 = !DILocation(line: 3, scope: !5)
+...
+---
+name:            _Z8call_memPPFvvE
+alignment:       16
+tracksRegLiveness: true
+noPhis:          true
+isSSA:           false
+noVRegs:         true
+hasFakeUses:     false
+debugInstrRef:   true
+tracksDebugUserValues: true
+liveins:
+  - { reg: '$rdi' }
+frameInfo:
+  stackSize:       8
+  offsetAdjustment: -8
+  maxAlignment:    1
+  adjustsStack:    true
+  hasCalls:        true
+  maxCallFrameSize: 0
+  cvBytesOfCalleeSavedRegisters: 8
+  isCalleeSavedInfoValid: true
+fixedStack:
+  - { id: 0, type: spill-slot, offset: -16, size: 8, alignment: 16, callee-saved-register: '$rbx' }
+machineFunctionInfo:
+  amxProgModel:    None
+body:             |
+  bb.0.entry:
+    liveins: $rdi, $rbx
+
+    frame-setup PUSH64r killed $rbx, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 16
+    CFI_INSTRUCTION offset $rbx, -16
+    $rbx = MOV64rr $rdi
+    CALL64m $rdi, 1, $noreg, 0, $noreg, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !13 :: (load (s64) from %ir.f)
+    CALL64m killed renamable $rbx, 1, $noreg, 0, $noreg, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !14 :: (load (s64) from %ir.f)
+    $rbx = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 8
+    RET64
+...
diff --git a/llvm/test/DebugInfo/X86/dwarf-call-target-mem-loc.mir b/llvm/test/DebugInfo/X86/dwarf-call-target-mem-loc.mir
index ef8a080cebaae..1923baf9fa173 100644
--- a/llvm/test/DebugInfo/X86/dwarf-call-target-mem-loc.mir
+++ b/llvm/test/DebugInfo/X86/dwarf-call-target-mem-loc.mir
@@ -1,10 +1,11 @@
 # RUN: llc %s --start-after=livedebugvalues -o - --filetype=obj | llvm-dwarfdump -  | FileCheck %s
 
 ## Check the memory location of the target address for the indirect call
-## (virtual in this case) is described by a DW_AT_call_target expression.
+## (virtual in this case) is described by a DW_AT_call_target_clobbered
+## expression.
 
 # CHECK: DW_TAG_call_site
-# CHECK-NEXT: DW_AT_call_target (DW_OP_breg0 RAX+8)
+# CHECK-NEXT: DW_AT_call_target_clobbered (DW_OP_breg0 RAX+8)
 
 ## Generated from this C++ with llc -stop-after=livedebugvalues -simplify-mir:
 ## struct Base {
diff --git a/llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs-indirect.ll b/llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs-indirect.ll
index f64b78f5820e4..70f91c66d4e58 100644
--- a/llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs-indirect.ll
+++ b/llvm/test/DebugInfo/X86/dwarf-callsite-related-attrs-indirect.ll
@@ -18,7 +18,7 @@ entry:
     #dbg_value(ptr %f, !17, !DIExpression(), !18)
 
 ; OBJ:   DW_TAG_call_site
-; OBJ:     DW_AT_call_target (DW_OP_reg[[#]] {{.*}})
+; OBJ:     DW_AT_call_target{{(_clobbered)?}} (DW_OP_reg[[#]] {{.*}})
 ; OBJ:     DW_AT_call_return_pc
   call void (...) %f() #1, !dbg !19
   ret void, !dbg !20
@@ -33,7 +33,7 @@ entry:
   %0 = load ptr, ptr %f, align 8, !dbg !28, !tbaa !29
 
 ; OBJ:   DW_TAG_call_site
-; OBJ:     DW_AT_call_target (DW_OP_breg[[#]] {{.*}})
+; OBJ:     DW_AT_call_target{{(_clobbered)?}} (DW_OP_breg[[#]] {{.*}})
 ; OBJ:     DW_AT_call_return_pc
   call void (...) %0() #1, !dbg !28
   ret void, !dbg !33

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants