[llvm-bugs] [Bug 48719] New: [DebugInfo at O2] Incorrect debug info for escaped variables after LowerDbgDeclare runs
via llvm-bugs
llvm-bugs at lists.llvm.org
Mon Jan 11 10:03:43 PST 2021
https://bugs.llvm.org/show_bug.cgi?id=48719
Bug ID: 48719
Summary: [DebugInfo at O2] Incorrect debug info for escaped
variables after LowerDbgDeclare runs
Product: libraries
Version: trunk
Hardware: PC
OS: All
Status: NEW
Severity: enhancement
Priority: P
Component: Transformation Utilities
Assignee: unassignedbugs at nondot.org
Reporter: orlando.hyams at sony.com
CC: llvm-bugs at lists.llvm.org
# TL;DR
llvm's debug info model is ineffective for variables which cannot be promoted
in the first round of SROA/mem2reg and we can see incorrect debug info being
generated as a result.
# Example
clang built at 1ecae1e62ad0 (10th Jan 2021).
We can get incorrect debug info for variables which are promoted (or partially
promoted) after InstCombine runs LowerDbgDeclare. I think this is a general
problem with the debug info model which manifests in more than one place (see
summary at end), but here is a specific example first:
$ cat -n test.c
1 void esc(int* p);
2 void fluff();
3 int fun(int first, int second) {
4 if (first)
5 first = second;
6 fluff();
7 esc(&first); //< first is escaped here.
8 return 0;
9 }
Compile it with the following command and look at the IR:
$ clang -O2 -g -emit-llvm -S -mllvm -print-after-all test.c -o -
define dso_local i32 @fun(i32 %first, i32 %second) local_unnamed_addr #0
!dbg !7 {
entry:
%first.addr = alloca i32, align 4
call void @llvm.dbg.value(metadata i32 %first, metadata !12, metadata
!DIExpression()), !dbg !14
call void @llvm.dbg.value(metadata i32 %second, metadata !13, metadata
!DIExpression()), !dbg !14
%tobool.not = icmp eq i32 %first, 0, !dbg !15
%spec.select = select i1 %tobool.not, i32 0, i32 %second, !dbg !17
store i32 %spec.select, i32* %first.addr, align 4, !tbaa !18
tail call void (...) @fluff() #3, !dbg !22
call void @llvm.dbg.value(metadata i32* %first.addr, metadata !12,
metadata !DIExpression(DW_OP_deref)), !dbg !14
call void @esc(i32* nonnull %first.addr) #3, !dbg !23
ret i32 0, !dbg !24
}
...
!12 = !DILocalVariable(name: "first", arg: 1, scope: !7, file: !1, line: 3,
type: !10)
!13 = !DILocalVariable(name: "second", arg: 2, scope: !7, file: !1, line:
3, type: !10)
Notice that the second dbg.value for "first" (!12) unexpectedly comes after the
call to "fluff()". If you run this example through a debugger you will see
"first=5" on line 6 when you'd expect "first=20". The dbg.value should instead
be positioned immediately after the select instruction.
# Example walkthrough
In the example "first" is escaped so it is not promoted by the early
SROA/mem2reg pass. InstCombine will partially promote it by merging the stores
(the initial store and the store on the if-then branch) into their common
successor by inserting a PHI and store. Later SimplifyCFG folds all of the
blocks together, turning the PHI into a select. InstCombine doesn't insert a
dbg.value after the PHI and SimplifyCFG doesn't insert one when converting the
PHI to a select, so the merged value is untracked in debug info.
We can see what happened by looking at the -print-after-all output.
SROA/mem2reg runs early and is not able to promote "first" because it is
escaped. So "first"'s alloca and dbg.declare survive:
*** IR Dump After SROA ***
define dso_local i32 @fun(i32 %first, i32 %second) #0 !dbg !7 {
entry:
%first.addr = alloca i32, align 4
store i32 %first, i32* %first.addr, align 4, !tbaa !14
call void @llvm.dbg.declare(metadata i32* %first.addr, metadata !12,
metadata !DIExpression()), !dbg !18
call void @llvm.dbg.value(metadata i32 %second, metadata !13, metadata
!DIExpression()), !dbg !19
%0 = load i32, i32* %first.addr, align 4, !dbg !20, !tbaa !14
%tobool = icmp ne i32 %0, 0, !dbg !20
br i1 %tobool, label %if.then, label %if.end, !dbg !22
if.then: ; preds = %entry
store i32 %second, i32* %first.addr, align 4, !dbg !23, !tbaa !14
br label %if.end, !dbg !24
if.end: ; preds = %if.then,
%entry
call void (...) @fluff(), !dbg !25
call void @esc(i32* %first.addr), !dbg !26
ret i32 0, !dbg !27
}
...
!12 = !DILocalVariable(name: "first", arg: 1, scope: !7, file: !1, line: 3,
type: !10)
!13 = !DILocalVariable(name: "second", arg: 2, scope: !7, file: !1, line:
3, type: !10)
Later, InstCombine runs and before doing any optimisations it calls
LowerDbgDeclare to convert the dbg.declare into a set of dbg.value intrinsics:
### IR after LowerDbgDeclare runs but before InstCombine starts combining
things ###
define dso_local i32 @fun(i32 %first, i32 %second) local_unnamed_addr #0
!dbg !7 {
entry:
%first.addr = alloca i32, align 4
call void @llvm.dbg.value(metadata i32 %first, metadata !12, metadata
!DIExpression()), !dbg !14
store i32 %first, i32* %first.addr, align 4, !tbaa !15
call void @llvm.dbg.value(metadata i32 %second, metadata !13, metadata
!DIExpression()), !dbg !14
%tobool = icmp ne i32 %first, 0, !dbg !19
br i1 %tobool, label %if.then, label %if.end, !dbg !21
if.then: ; preds = %entry
call void @llvm.dbg.value(metadata i32 %second, metadata !12, metadata
!DIExpression()), !dbg !14
store i32 %second, i32* %first.addr, align 4, !dbg !22, !tbaa !15
br label %if.end, !dbg !23
if.end: ; preds = %if.then,
%entry
call void (...) @fluff(), !dbg !24
call void @llvm.dbg.value(metadata i32* %first.addr, metadata !12,
metadata !DIExpression(DW_OP_deref)), !dbg !14
call void @esc(i32* %first.addr), !dbg !25
ret i32 0, !dbg !26
}
...
!12 = !DILocalVariable(name: "first", arg: 1, scope: !7, file: !1, line: 3,
type: !10)
!13 = !DILocalVariable(name: "second", arg: 2, scope: !7, file: !1, line:
3, type: !10)
InstCombine merges the two stores to %first.addr ("first"'s alloca address)
into the common successor "if.end" as a PHI and store. Notice how it does not
insert a dbg.value to describe the merged value %storemerge.
*** IR Dump After Combine redundant instructions ***
define dso_local i32 @fun(i32 %first, i32 %second) local_unnamed_addr #0
!dbg !7 {
entry:
%first.addr = alloca i32, align 4
call void @llvm.dbg.value(metadata i32 %first, metadata !12, metadata
!DIExpression()), !dbg !14
call void @llvm.dbg.value(metadata i32 %second, metadata !13, metadata
!DIExpression()), !dbg !14
%tobool.not = icmp eq i32 %first, 0, !dbg !15
br i1 %tobool.not, label %if.end, label %if.then, !dbg !17
if.then: ; preds = %entry
call void @llvm.dbg.value(metadata i32 %second, metadata !12, metadata
!DIExpression()), !dbg !14
br label %if.end, !dbg !18
if.end: ; preds = %if.then,
%entry
%storemerge = phi i32 [ %second, %if.then ], [ %first, %entry ]
store i32 %storemerge, i32* %first.addr, align 4, !tbaa !19
call void (...) @fluff() #3, !dbg !23
call void @llvm.dbg.value(metadata i32* %first.addr, metadata !12,
metadata !DIExpression(DW_OP_deref)), !dbg !14
call void @esc(i32* nonnull %first.addr) #3, !dbg !24
ret i32 0, !dbg !25
}
...
!12 = !DILocalVariable(name: "first", arg: 1, scope: !7, file: !1, line: 3,
type: !10)
!13 = !DILocalVariable(name: "second", arg: 2, scope: !7, file: !1, line:
3, type: !10)
SimplifyCFG folds these blocks together, converting the PHI to a select, and
leaves us with the incorrect debug info shown earlier:
*** IR Dump After Simplify the CFG ***
define dso_local i32 @fun(i32 %first, i32 %second) local_unnamed_addr #0
!dbg !7 {
entry:
%first.addr = alloca i32, align 4
call void @llvm.dbg.value(metadata i32 %first, metadata !12, metadata
!DIExpression()), !dbg !14
call void @llvm.dbg.value(metadata i32 %second, metadata !13, metadata
!DIExpression()), !dbg !14
%tobool.not = icmp eq i32 %first, 0, !dbg !15
%spec.select = select i1 %tobool.not, i32 %first, i32 %second, !dbg !17
store i32 %spec.select, i32* %first.addr, align 4, !tbaa !18
call void (...) @fluff() #3, !dbg !22
call void @llvm.dbg.value(metadata i32* %first.addr, metadata !12,
metadata !DIExpression(DW_OP_deref)), !dbg !14
call void @esc(i32* nonnull %first.addr) #3, !dbg !23
ret i32 0, !dbg !24
}
...
!12 = !DILocalVariable(name: "first", arg: 1, scope: !7, file: !1, line: 3,
type: !10)
!13 = !DILocalVariable(name: "second", arg: 2, scope: !7, file: !1, line:
3, type: !10)
# Summary
This example shows that InstCombine and SimplifyCFG compose to produce
incorrect debug info for escaped variables. However, the reason that I think
this is a general problem rather than a specific bug, and why I am struggling
to hold any one pass accountable, is because of the following inconsistencies:
If "first" in this example can be fully promoted by mem2reg
(https://godbolt.org/z/Es89z7) then there is no issue because mem2reg will
insert a dbg.value after the PHI which eventually becomes the select.
However, mem2reg can also cause the same bug because it only inserts dbg.values
when promoting a variable if and only if it has a dbg.declare. It's possible
for LowerDbgDeclare to remove the dbg.declare before mem2reg runs. For example,
when a variable is escaped but the escaping function is later inlined. You can
see this happening here https://godbolt.org/z/KrdjGs.
Lastly, we don't see this issue in any of these cases if there isn't any block
folding because LiveDebugValues will merge live-out variable locations from
preds (https://godbolt.org/z/sjcnbv).
As far as I can tell there are no rules or examples demonstrating how debug
info should be updated when promoting or partially promoting variables after
LowerDbgDeclare has removed the dbg.declare.
--
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20210111/cf235c34/attachment-0001.html>
More information about the llvm-bugs
mailing list