[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