Skip to content

Commit a8dedbc

Browse files
jmorseChenyang-L
authored andcommitted
[DebugInfo][InstrRef] Instrument x86 CMOV conversion to preserve variable values
X86's CMOV conversion transforms CMOV instructions into control flow between blocks, meaning the value is computed by a PHI rather than a "real" machine instruction. In instruction-referencing mode, we need to transfer the instruction label between the old CMOV and the new PHI instruction to mark where the variable value is computed. There's an extra complication in that memory operands can be unfolded from the CMOV and sunk into the new blocks -- the test checks both scenarios where the instruction number has to hop between instructions. This omission exposed by Dexter testing. Reviewed By: Orlando Differential Revision: https://reviews.llvm.org/D145565
1 parent 14a85f2 commit a8dedbc

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

llvm/lib/Target/X86/X86CmovConversion.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,8 @@ void X86CmovConverterPass::convertCmovInstsToBranches(
774774
const TargetRegisterClass *RC = MRI->getRegClass(MI.getOperand(0).getReg());
775775
Register TmpReg = MRI->createVirtualRegister(RC);
776776

777+
// Retain debug instr number when unfolded.
778+
unsigned OldDebugInstrNum = MI.peekDebugInstrNum();
777779
SmallVector<MachineInstr *, 4> NewMIs;
778780
bool Unfolded = TII->unfoldMemoryOperand(*MBB->getParent(), MI, TmpReg,
779781
/*UnfoldLoad*/ true,
@@ -791,6 +793,9 @@ void X86CmovConverterPass::convertCmovInstsToBranches(
791793
if (&*MIItBegin == &MI)
792794
MIItBegin = MachineBasicBlock::iterator(NewCMOV);
793795

796+
if (OldDebugInstrNum)
797+
NewCMOV->setDebugInstrNum(OldDebugInstrNum);
798+
794799
// Sink whatever instructions were needed to produce the unfolded operand
795800
// into the false block.
796801
for (auto *NewMI : NewMIs) {
@@ -858,6 +863,11 @@ void X86CmovConverterPass::convertCmovInstsToBranches(
858863
LLVM_DEBUG(dbgs() << "\tFrom: "; MIIt->dump());
859864
LLVM_DEBUG(dbgs() << "\tTo: "; MIB->dump());
860865

866+
// debug-info: we can just copy the instr-ref number from one instruction
867+
// to the other, seeing how it's a one-for-one substitution.
868+
if (unsigned InstrNum = MIIt->peekDebugInstrNum())
869+
MIB->setDebugInstrNum(InstrNum);
870+
861871
// Add this PHI to the rewrite table.
862872
RegRewriteTable[DestReg] = std::make_pair(Op1Reg, Op2Reg);
863873
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# RUN: llc %s -o - --run-pass=x86-cmov-conversion -mtriple=x86_64-- | FileCheck %s
2+
# REQUIRES: x86-registered-target
3+
#
4+
# Check that when we create new blocks and PHIs to represent CMOVs, that the
5+
# debug-instr-number is replaced on the new instruction, to preserve variable
6+
# locations. Check that this still works when unfolding memory operands, which
7+
# involves more decomposition of instructions.
8+
#
9+
# CHECK-LABEL: name: CmovInHotPath
10+
# CHECK-LABEL: bb.3.for.body:
11+
# CHECK: CMOV32rr {{.*}}, debug-instr-number 1
12+
#
13+
# CHECK-LABEL: name: test_cmov_memoperand_in_group_reuse_for_addr2
14+
# CHECK-LABEL: bb.2.entry:
15+
# CHECK-NEXT: PHI {{.*}} debug-instr-number 1,
16+
# CHECK-NEXT: PHI {{.*}} debug-instr-number 2,
17+
--- |
18+
; ModuleID = 'x86-cmov-converter.ll'
19+
source_filename = "x86-cmov-converter.ll"
20+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
21+
22+
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
23+
declare void @llvm.dbg.value(metadata, metadata, metadata) #0
24+
25+
define void @CmovInHotPath(i32 %n, i32 %a, i32 %b, ptr nocapture %c, ptr nocapture readnone %d) !dbg !7 {
26+
entry:
27+
%cmp14 = icmp sgt i32 %n, 0, !dbg !12
28+
br i1 %cmp14, label %for.body.preheader, label %for.cond.cleanup
29+
30+
for.body.preheader: ; preds = %entry
31+
%wide.trip.count = zext i32 %n to i64, !dbg !13
32+
br label %for.body
33+
34+
for.cond.cleanup: ; preds = %for.body, %entry
35+
ret void
36+
37+
for.body: ; preds = %for.body, %for.body.preheader
38+
%lsr.iv1 = phi ptr [ %uglygep, %for.body ], [ %c, %for.body.preheader ]
39+
%lsr.iv = phi i64 [ %lsr.iv.next, %for.body ], [ %wide.trip.count, %for.body.preheader ]
40+
%0 = load i32, ptr %lsr.iv1, align 4, !dbg !13
41+
%add = add nsw i32 %0, 1, !dbg !13
42+
%mul = mul nsw i32 %0, %a, !dbg !13
43+
%cmp3 = icmp sgt i32 %mul, %b, !dbg !13
44+
%. = select i1 %cmp3, i32 10, i32 %add, !dbg !13
45+
call void @llvm.dbg.value(metadata i32 %., metadata !14, metadata !DIExpression()), !dbg !13
46+
%mul7 = mul nsw i32 %., %add, !dbg !13
47+
store i32 %mul7, ptr %lsr.iv1, align 4, !dbg !13
48+
%lsr.iv.next = add nsw i64 %lsr.iv, -1, !dbg !13
49+
%uglygep = getelementptr i8, ptr %lsr.iv1, i64 4, !dbg !13
50+
%exitcond = icmp eq i64 %lsr.iv.next, 0, !dbg !13
51+
br i1 %exitcond, label %for.cond.cleanup, label %for.body, !dbg !13
52+
}
53+
54+
define i32 @test_cmov_memoperand_in_group_reuse_for_addr2(i32 %a, i32 %b, ptr %x, ptr %y) !dbg !15 {
55+
entry:
56+
%cond = icmp ugt i32 %a, %b, !dbg !16
57+
%load1 = load ptr, ptr %y, align 8, !dbg !16
58+
%p = select i1 %cond, ptr %x, ptr %load1, !dbg !16
59+
call void @llvm.dbg.value(metadata ptr %p, metadata !17, metadata !DIExpression()), !dbg !16
60+
%load2 = load i32, ptr %p, align 4, !dbg !16
61+
%z = select i1 %cond, i32 %a, i32 %load2, !dbg !16
62+
call void @llvm.dbg.value(metadata i32 %z, metadata !18, metadata !DIExpression()), !dbg !16
63+
ret i32 %z, !dbg !16
64+
}
65+
66+
attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
67+
68+
!llvm.dbg.cu = !{!0}
69+
!llvm.module.flags = !{!3, !4, !5}
70+
!llvm.ident = !{!6}
71+
72+
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
73+
!1 = !DIFile(filename: "test.c", directory: "/tmp/out.c")
74+
!2 = !{}
75+
!3 = !{i32 7, !"Dwarf Version", i32 4}
76+
!4 = !{i32 2, !"Debug Info Version", i32 3}
77+
!5 = !{i32 1, !"wchar_size", i32 4}
78+
!6 = !{!""}
79+
!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
80+
!8 = !DISubroutineType(types: !9)
81+
!9 = !{!10, !11, !11}
82+
!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
83+
!11 = !DIBasicType(name: "long int", size: 64, encoding: DW_ATE_signed)
84+
!12 = !DILocation(line: 16, column: 3, scope: !7)
85+
!13 = !DILocation(line: 15, column: 3, scope: !7)
86+
!14 = !DILocalVariable(name: "bar", arg: 1, scope: !7, file: !1, line: 3, type: !11)
87+
!15 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
88+
!16 = !DILocation(line: 16, column: 3, scope: !15)
89+
!17 = !DILocalVariable(name: "bar", arg: 1, scope: !15, file: !1, line: 3, type: !11)
90+
!18 = !DILocalVariable(name: "baz", arg: 2, scope: !15, file: !1, line: 3, type: !11)
91+
92+
...
93+
---
94+
name: CmovInHotPath
95+
tracksRegLiveness: true
96+
debugInstrRef: true
97+
registers:
98+
- { id: 0, class: gr64, preferred-register: '' }
99+
- { id: 1, class: gr64, preferred-register: '' }
100+
- { id: 2, class: gr64, preferred-register: '' }
101+
- { id: 3, class: gr64, preferred-register: '' }
102+
- { id: 4, class: gr64, preferred-register: '' }
103+
- { id: 5, class: gr32, preferred-register: '' }
104+
- { id: 6, class: gr32, preferred-register: '' }
105+
- { id: 7, class: gr32, preferred-register: '' }
106+
liveins:
107+
- { reg: '$edi', virtual-reg: '%5' }
108+
- { reg: '$esi', virtual-reg: '%6' }
109+
- { reg: '$edx', virtual-reg: '%7' }
110+
- { reg: '$rcx', virtual-reg: '%8' }
111+
machineFunctionInfo: {}
112+
body: |
113+
bb.0.entry:
114+
successors: %bb.1(0x50000000), %bb.2(0x30000000)
115+
liveins: $edi, $esi, $edx, $rcx
116+
117+
%8:gr64 = COPY $rcx
118+
%7:gr32 = COPY $edx
119+
%6:gr32 = COPY $esi
120+
%5:gr32 = COPY $edi
121+
TEST32rr %5, %5, implicit-def $eflags, debug-location !12
122+
JCC_1 %bb.2, 14, implicit $eflags
123+
JMP_1 %bb.1
124+
125+
bb.1.for.body.preheader:
126+
successors: %bb.3(0x80000000)
127+
128+
%10:gr32 = MOV32rr %5, debug-location !13
129+
%0:gr64 = SUBREG_TO_REG 0, killed %10, %subreg.sub_32bit, debug-location !13
130+
JMP_1 %bb.3
131+
132+
bb.2.for.cond.cleanup:
133+
RET 0
134+
135+
bb.3.for.body:
136+
successors: %bb.2(0x04000000), %bb.3(0x7c000000)
137+
138+
%1:gr64 = PHI %8, %bb.1, %4, %bb.3
139+
%2:gr64 = PHI %0, %bb.1, %3, %bb.3
140+
%11:gr32 = MOV32rm %1, 1, $noreg, 0, $noreg, debug-location !13 :: (load (s32) from %ir.lsr.iv1)
141+
%12:gr32 = nsw INC32r %11, implicit-def dead $eflags, debug-location !13
142+
%13:gr32 = nsw IMUL32rr %11, %6, implicit-def dead $eflags, debug-location !13
143+
%14:gr32 = SUB32rr %13, %7, implicit-def $eflags, debug-location !13
144+
%15:gr32 = MOV32ri 10
145+
%16:gr32 = CMOV32rr %12, killed %15, 15, implicit $eflags, debug-instr-number 1, debug-location !13
146+
DBG_INSTR_REF !14, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(1, 0), debug-location !13
147+
%17:gr32 = nsw IMUL32rr %16, %12, implicit-def dead $eflags, debug-location !13
148+
MOV32mr %1, 1, $noreg, 0, $noreg, killed %17, debug-location !13 :: (store (s32) into %ir.lsr.iv1)
149+
%4:gr64 = ADD64ri8 %1, 4, implicit-def dead $eflags, debug-location !13
150+
%3:gr64 = DEC64r %2, implicit-def $eflags, debug-location !13
151+
JCC_1 %bb.2, 4, implicit $eflags, debug-location !13
152+
JMP_1 %bb.3, debug-location !13
153+
154+
...
155+
---
156+
name: test_cmov_memoperand_in_group_reuse_for_addr2
157+
alignment: 16
158+
tracksRegLiveness: true
159+
debugInstrRef: true
160+
registers:
161+
- { id: 0, class: gr32, preferred-register: '' }
162+
- { id: 1, class: gr32, preferred-register: '' }
163+
- { id: 2, class: gr64, preferred-register: '' }
164+
- { id: 3, class: gr64, preferred-register: '' }
165+
- { id: 4, class: gr32, preferred-register: '' }
166+
- { id: 5, class: gr64, preferred-register: '' }
167+
- { id: 6, class: gr32, preferred-register: '' }
168+
liveins:
169+
- { reg: '$edi', virtual-reg: '%0' }
170+
- { reg: '$esi', virtual-reg: '%1' }
171+
- { reg: '$rdx', virtual-reg: '%2' }
172+
- { reg: '$rcx', virtual-reg: '%3' }
173+
debugValueSubstitutions: []
174+
constants: []
175+
machineFunctionInfo: {}
176+
body: |
177+
bb.0.entry:
178+
liveins: $edi, $esi, $rdx, $rcx
179+
180+
%3:gr64 = COPY $rcx
181+
%2:gr64 = COPY $rdx
182+
%1:gr32 = COPY $esi
183+
%0:gr32 = COPY $edi
184+
%4:gr32 = SUB32rr %0, %1, implicit-def $eflags, debug-location !16
185+
%5:gr64 = CMOV64rm %2, %3, 1, $noreg, 0, $noreg, 6, implicit $eflags, debug-instr-number 1, debug-location !16 :: (load (s64) from %ir.y)
186+
DBG_INSTR_REF !17, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(1, 0), debug-location !16
187+
%6:gr32 = CMOV32rm %0, killed %5, 1, $noreg, 0, $noreg, 6, implicit $eflags, debug-instr-number 2, debug-location !16 :: (load (s32) from %ir.p)
188+
DBG_INSTR_REF !18, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(2, 0), debug-location !16
189+
$eax = COPY %6, debug-location !16
190+
RET 0, $eax, debug-location !16
191+
192+
...

0 commit comments

Comments
 (0)