Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[InstrRef] Skip clobbered EntryValue register recovery #77938

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

felipepiovezan
Copy link
Contributor

This changes the final stage of InstrRef, i.e. the TransferTracker (which combines the values locations with the variable values), so that it treats a DEBUG_VALUE of an EntryValue just like a DEBUG_VALUE of a constant: a location that is never clobbered and can be propagated to subsequent BBs as long as no other DEBUG_VALUE intrinsics updated the variable.

We add two tests here:

  1. entry_value_clobbered_stack_copy that saves a register on the stack, uses this register as an entry value DBG_VALUE location, and then clobbers it. Prior to this patch, this test would crash because we would try to describe a new location for the variable in terms of what was saved on the stack, and use an invalid expression to do so. This is not needed as an EntryValue can never be clobbered.

  2. entry_value_gets_propagated, that tests that an EntryValue DBG_VALUE is propagated in a diamond-shaped CFG. This test works with or without this patch, but it reveals a bug (see the FIXME), where the propagation doesn't happen if the register gets clobbered. This same test (with the clobbered register) works fine in the VarLoc implementation of LDV.

@llvmbot
Copy link
Collaborator

llvmbot commented Jan 12, 2024

@llvm/pr-subscribers-debuginfo

Author: Felipe de Azevedo Piovezan (felipepiovezan)

Changes

This changes the final stage of InstrRef, i.e. the TransferTracker (which combines the values locations with the variable values), so that it treats a DEBUG_VALUE of an EntryValue just like a DEBUG_VALUE of a constant: a location that is never clobbered and can be propagated to subsequent BBs as long as no other DEBUG_VALUE intrinsics updated the variable.

We add two tests here:

  1. entry_value_clobbered_stack_copy that saves a register on the stack, uses this register as an entry value DBG_VALUE location, and then clobbers it. Prior to this patch, this test would crash because we would try to describe a new location for the variable in terms of what was saved on the stack, and use an invalid expression to do so. This is not needed as an EntryValue can never be clobbered.

  2. entry_value_gets_propagated, that tests that an EntryValue DBG_VALUE is propagated in a diamond-shaped CFG. This test works with or without this patch, but it reveals a bug (see the FIXME), where the propagation doesn't happen if the register gets clobbered. This same test (with the clobbered register) works fine in the VarLoc implementation of LDV.


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

3 Files Affected:

  • (modified) llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp (+1)
  • (added) llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir (+53)
  • (added) llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir (+102)
diff --git a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
index cfc8c28b99e562..71a0255bf36e17 100644
--- a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
+++ b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
@@ -670,6 +670,7 @@ class TransferTracker {
 
     // Ignore non-register locations, we don't transfer those.
     if (MI.isUndefDebugValue() ||
+        MI.getDebugExpression()->isEntryValue() ||
         all_of(MI.debug_operands(),
                [](const MachineOperand &MO) { return !MO.isReg(); })) {
       auto It = ActiveVLocs.find(Var);
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
new file mode 100644
index 00000000000000..1280045a0a1cf1
--- /dev/null
+++ b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_clobbered_stack_copy.mir
@@ -0,0 +1,53 @@
+# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
+# REQUIRES: x86-registered-target
+
+--- |
+  target triple = "x86_64-"
+  define void @foo(ptr swiftasync %0) !dbg !4 {
+    call void @llvm.dbg.value(metadata ptr %0, metadata !9, metadata !DIExpression(DW_OP_LLVM_entry_value, 1)), !dbg !17
+    ret void
+  }
+  declare void @llvm.dbg.value(metadata, metadata, metadata)
+
+  !llvm.module.flags = !{!0}
+  !llvm.dbg.cu = !{!1}
+
+  !0 = !{i32 2, !"Debug Info Version", i32 3}
+  !1 = distinct !DICompileUnit(language: DW_LANG_Swift, file: !2, producer: "blah", isOptimized: true, flags: "blah", runtimeVersion: 5, emissionKind: FullDebug)
+  !2 = !DIFile(filename: "blah", directory: "blah")
+  !3 = !{}
+  !4 = distinct !DISubprogram(name: "blah", linkageName: "blah", scope: !2, file: !2, line: 284, type: !7, unit: !1)
+  !7 = !DISubroutineType(types: !3)
+  !9 = !DILocalVariable(name: "self", arg: 3, scope: !4, file: !2, line: 328, type: !12, flags: DIFlagArtificial)
+  !12 = !DICompositeType(tag: DW_TAG_structure_type, name: "blah", scope: !2, file: !2, size: 64, elements: !3)
+  !17 = !DILocation(line: 328, column: 17, scope: !4)
+
+...
+---
+name:            foo
+alignment:       16
+debugInstrRef:   true
+tracksDebugUserValues: true
+liveins:
+  - { reg: '$r14', virtual-reg: '' }
+stack:
+  - { id: 0, name: '', type: spill-slot, offset: -64, size: 8, alignment: 8,
+      stack-id: default, callee-saved-register: '', callee-saved-restored: true,
+      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
+body:             |
+  bb.0:
+    liveins: $r14
+
+    ; Put a copy of r14 on the stack.
+    MOV64mr $rbp, 1, $noreg, -48, $noreg, $r14 :: (store (s64) into %stack.0)
+    DBG_VALUE $r14, $noreg, !9, !DIExpression(DW_OP_LLVM_entry_value, 1), debug-location !17
+    MOV64mi32 $noreg, 1, $noreg, 0, $noreg, 0, debug-location !17 :: (store (s64) into `ptr null`)
+    $r14 = MOV64rr killed $r13
+    ; Clobber $r14
+    RETI64 24
+
+# CHECK: bb.0:
+# CHECK:      MOV64mr $rbp, 1, $noreg, -48, $noreg, $r14
+# CHECK-NEXT: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
+# CHECK-NOT:  DBG_VALUE
+...
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
new file mode 100644
index 00000000000000..4fa1485a75d2ce
--- /dev/null
+++ b/llvm/test/DebugInfo/MIR/InstrRef/entry_value_gets_propagated.mir
@@ -0,0 +1,102 @@
+# RUN: llc --run-pass=livedebugvalues -o - %s | FileCheck %s
+# REQUIRES: x86-registered-target
+--- |
+  target triple = "x86_64-"
+
+  define i32 @baz(i32 swiftasync %arg1, i32 noundef %arg2, i1 %cond) !dbg !9 {
+    tail call void @llvm.dbg.value(metadata i32 %arg1, metadata !17, metadata !DIExpression(DW_OP_LLVM_entry_value, 1)), !dbg !19
+    br i1 %cond, label %if.then, label %if.else, !dbg !22
+  if.then:
+    %call = call i32 @foo(i32 noundef %arg1), !dbg !23
+    br label %if.end, !dbg !25
+  if.else:
+    %call1 = call i32 @foo(i32 noundef %arg2), !dbg !26
+    br label %if.end
+  if.end:
+    %temp.0 = phi i32 [ %call, %if.then ], [ %call1, %if.else ], !dbg !28
+    ret i32 %temp.0, !dbg !29
+  }
+
+  declare i32 @foo(i32)
+  declare void @llvm.dbg.value(metadata, metadata, metadata)
+
+  !llvm.dbg.cu = !{!0}
+  !llvm.module.flags = !{!2, !3}
+
+  !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "ha", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+  !1 = !DIFile(filename: "test.c", directory: "hah")
+  !2 = !{i32 7, !"Dwarf Version", i32 4}
+  !3 = !{i32 2, !"Debug Info Version", i32 3}
+  !9 = distinct !DISubprogram(name: "baz", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, unit: !0, retainedNodes: !13)
+  !10 = !DISubroutineType(types: !11)
+  !11 = !{!12, !12, !12, !12}
+  !12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+  !13 = !{!14, !15, !16, !17}
+  !14 = !DILocalVariable(name: "arg1", arg: 1, scope: !9, file: !1, line: 3, type: !12)
+  !15 = !DILocalVariable(name: "arg2", arg: 2, scope: !9, file: !1, line: 3, type: !12)
+  !16 = !DILocalVariable(name: "cond", arg: 3, scope: !9, file: !1, line: 3, type: !12)
+  !17 = !DILocalVariable(name: "local", scope: !9, file: !1, line: 4, type: !12)
+  !19 = !DILocation(line: 0, scope: !9)
+  !20 = !DILocation(line: 7, column: 7, scope: !21)
+  !21 = distinct !DILexicalBlock(scope: !9, file: !1, line: 7, column: 7)
+  !22 = !DILocation(line: 7, column: 7, scope: !9)
+  !23 = !DILocation(line: 8, column: 12, scope: !24)
+  !24 = distinct !DILexicalBlock(scope: !21, file: !1, line: 7, column: 13)
+  !25 = !DILocation(line: 9, column: 3, scope: !24)
+  !26 = !DILocation(line: 10, column: 12, scope: !27)
+  !27 = distinct !DILexicalBlock(scope: !21, file: !1, line: 9, column: 10)
+  !28 = !DILocation(line: 0, scope: !21)
+  !29 = !DILocation(line: 13, column: 3, scope: !9)
+
+...
+---
+name:            baz
+alignment:       16
+debugInstrRef: true
+tracksDebugUserValues: true
+liveins:
+  - { reg: '$r14', virtual-reg: '' }
+  - { reg: '$edi', virtual-reg: '' }
+  - { reg: '$esi', virtual-reg: '' }
+  - { reg: '$edx', virtual-reg: '' }
+body:             |
+  bb.0:
+    successors: %bb.2(0x40000000), %bb.1(0x40000000)
+    liveins: $r14, $edi, $edx, $esi
+
+    DBG_VALUE $r14, $noreg, !14, !DIExpression(DW_OP_LLVM_entry_value, 1), debug-location !19
+    ; FIXME: InstrRef is not yet capable of propagating these when the entry
+    ; value register is clobbered.
+    ;$r14 = MOV64ri 0, debug-location !20
+    CMP32ri killed renamable $edx, 0, implicit-def $eflags, debug-location !20
+    JCC_1 %bb.2, 4, implicit killed $eflags, debug-location !22
+
+  bb.1.if.then:
+    successors: %bb.3(0x80000000)
+    liveins: $edi, $r13
+
+    CALL64pcrel32 @foo, csr_64, implicit $rsp, implicit $ssp, implicit $edi, implicit-def $eax, debug-location !23
+    JMP_1 %bb.3, debug-location !25
+
+  bb.2.if.else:
+    successors: %bb.3(0x80000000)
+    liveins: $esi, $r13
+
+    $edi = MOV32rr killed $esi, debug-location !26
+    CALL64pcrel32 @foo, csr_64, implicit $rsp, implicit $ssp, implicit $edi, implicit-def $eax, debug-location !26
+
+  bb.3.if.end:
+    liveins: $eax
+
+    $rbp = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !29
+    RET64 implicit $eax, debug-location !29
+
+# CHECK-LABEL: bb.0:
+# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
+# CHECK-LABEL: bb.1.if.then:
+# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
+# CHECK-LABEL: bb.2.if.else:
+# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
+# CHECK-LABEL: bb.3.if.end:
+# CHECK: DBG_VALUE $r14, {{.*}}, !DIExpression(DW_OP_LLVM_entry_value, 1)
+...

Copy link

github-actions bot commented Jan 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

This changes the final stage of InstrRef, i.e. the TransferTracker (which
combines the values locations with the variable values), so that it treats a
DEBUG_VALUE of an EntryValue just like a DEBUG_VALUE of a constant: a location
that is never clobbered and can be propagated to subsequent BBs as long as no
other DEBUG_VALUE intrinsics updated the variable.

We add two tests here:

1. `entry_value_clobbered_stack_copy` that saves a register on the stack, uses
this register as an entry value DBG_VALUE location, and then clobbers it. Prior
to this patch, this test would crash because we would try to describe a new
location for the variable in terms of what was saved on the stack, and use an
invalid expression to do so. This is not needed as an EntryValue can never be
clobbered.

2. `entry_value_gets_propagated`, that tests that an EntryValue DBG_VALUE is
propagated in a diamond-shaped CFG. This test works with or without this patch,
but it reveals a bug (see the FIXME), where the propagation doesn't happen if
the register gets clobbered. This same test (with the clobbered register) works
fine in the VarLoc implementation of LDV.
@felipepiovezan felipepiovezan force-pushed the felipe/instrref_skip_entry_value_clobbering branch from 46703fe to ed7b297 Compare January 12, 2024 15:35
Copy link
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; I guess there was no accounting for entry-value expressions not generated prior to LiveDebugValues when this was written :(.

Does that mean the middle-bits of InstrRefBasedLDV propagating variable locations are going to eventually need to handle incoming entry-values? That could mean some special juggling is required, but I think it should fit into the overall model.

@felipepiovezan
Copy link
Contributor Author

Does that mean the middle-bits of InstrRefBasedLDV propagating variable locations are going to eventually need to handle incoming entry-values? That could mean some special juggling is required, but I think it should fit into the overall model.

Yup, there is some work to do to get that second test to work with the clobbering line uncommented. Sadly I haven't been able to figure out how, but it feels like it should be possible

@jmorse
Copy link
Member

jmorse commented Jan 12, 2024

Yup, there is some work to do to get that second test to work with the clobbering line uncommented. Sadly I haven't been able to figure out how, but it feels like it should be possible

Shooting from the hip -- I reckon you could interpret entry-value DBG_VALUEs into being a ValueIDNum value-number when they're observed by transferDebugValue. The middle segment of InstrRefBasedLDV all works on a basis of effectively-SSA values identified with ValueIDNums, propagating those values doesn't consider registers or liveness, only the values. So it shooouuulllddd (confidence: 90%) be possible to write in transferDebugValue something like:

if (is-an-entry-value-expr) {
  LocIdx L = MTracker->getRegMLoc(MI.getOperand(0));
  ValueIDNum TheValue(0, 0, L);
  VTracker->defVar(MI, Props, {TheValue});
  return;
}

Or something like that. This works because we know entry-value DBG_VALUEs always refer to the live-in value at the entry block, the block-number and instruction-number of which are well-known as zero. Thus we effectively "know the name" of the value and can pretend it's like a normal DBG_INSTR_REF or DBG_VALUE that can refer to a non-live value. It might need the entry-value parts of the expression stripping out of the expression. Another way of thinking about it is like a DBG_INSTR_REF referring to well-known pre-determined instruction/operand numbers defined for entry values.

I think (confidence: 97%) that will just flow through the SSA parts as normal, then pop out the far end as a variable that's assigned a non-live value, which Should (TM) then be described using an entry value expression again.

That's some non-trivial jumping through hoops, but on the other hand it means variable assignments using DW_OP_entry_value earlier in the compiler will compose seamlessly with variable assignments that co-incidentally lose their liveness and need expressing as an entry value.

(It does seem incredibly dirty to use some constant magic values when constructing the ValueIDNum, we could probably make named-constants to make it read better. Alas, Jeremy is secretly a C programmer who's happiest when dealing with massive arrays of integers!)

@felipepiovezan
Copy link
Contributor Author

Yup, there is some work to do to get that second test to work with the clobbering line uncommented. Sadly I haven't been able to figure out how, but it feels like it should be possible

Shooting from the hip -- I reckon you could interpret entry-value DBG_VALUEs into being a ValueIDNum value-number when they're observed by transferDebugValue. The middle segment of InstrRefBasedLDV all works on a basis of effectively-SSA values identified with ValueIDNums, propagating those values doesn't consider registers or liveness, only the values. So it shooouuulllddd (confidence: 90%) be possible to write in transferDebugValue something like:

if (is-an-entry-value-expr) {
  LocIdx L = MTracker->getRegMLoc(MI.getOperand(0));
  ValueIDNum TheValue(0, 0, L);
  VTracker->defVar(MI, Props, {TheValue});
  return;
}

Or something like that. This works because we know entry-value DBG_VALUEs always refer to the live-in value at the entry block, the block-number and instruction-number of which are well-known as zero. Thus we effectively "know the name" of the value and can pretend it's like a normal DBG_INSTR_REF or DBG_VALUE that can refer to a non-live value. It might need the entry-value parts of the expression stripping out of the expression. Another way of thinking about it is like a DBG_INSTR_REF referring to well-known pre-determined instruction/operand numbers defined for entry values.

I think (confidence: 97%) that will just flow through the SSA parts as normal, then pop out the far end as a variable that's assigned a non-live value, which Should (TM) then be described using an entry value expression again.

That's some non-trivial jumping through hoops, but on the other hand it means variable assignments using DW_OP_entry_value earlier in the compiler will compose seamlessly with variable assignments that co-incidentally lose their liveness and need expressing as an entry value.

(It does seem incredibly dirty to use some constant magic values when constructing the ValueIDNum, we could probably make named-constants to make it read better. Alas, Jeremy is secretly a C programmer who's happiest when dealing with massive arrays of integers!)

Thank you for the ideas, I am working on trying this right now! Currently stuck because defVar wants a an ArrayRef<DbgOpID>, and not an ArrayRef< ValueIDNum>; I'm looking into how we can convert from one to the other.

By the way, since we are tackling separate problems here, I will likely merge this PR first and address the propagation issue separately!

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.

None yet

4 participants