[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