[DebugInfo][DWARF] Use DW_AT_call_target_clobbered for exprs with volatile regs (#172167)

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.
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
index d19de7f..c0421e6 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
@@ -1328,15 +1328,22 @@
 
   // 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 0000000..ff5c78d
--- /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 ef8a080..1923baf 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 f64b78f..70f91c6 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 @@
     #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 @@
   %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