Bug 105007 - EVRP lattice propagation sometimes makes a var in debug info not available
Summary: EVRP lattice propagation sometimes makes a var in debug info not available
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: tree-optimization (show other bugs)
Version: 12.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: wrong-debug
Depends on:
Blocks:
 
Reported: 2022-03-21 19:43 UTC by Cristian Assaiante
Modified: 2022-03-25 07:11 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2022-03-21 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Cristian Assaiante 2022-03-21 19:43:07 UTC
In this minimized C example, variables i and l_3, defined within the scope of the inlined function foo, are associated with DWARF symbols having possibly wrong location definition, which causes variables to not be available during debugging (in the example, the variables are used as call arguments to a function defined in an external module). We observe the bug at -O2 and -O3 with the root cause possibly being inlining. Please find below a detailed analysis for -O2 (with -O3 DWARF info looks identical) on x64, including a comparison with past gcc versions where the bug is sometimes not present.

Furthermore, once we make both variables appear by disabling some optimizations, the program reveals a possible bug in gdb, which ends up displaying an incorrect value for variable i while lldb shows the correct one. We are filing a separate bug report, linking it in a comment below, as the behavior is quite articulated.

$ cat a.c
void foo()
{ 
   int l_3 = 5, i = 0;
   for (; i < 8; i++)
       ;
   test(l_3, i);
}
int main()
{
   foo();
}
$ cat lib.c
#include <stdio.h>

void test(int l_3, int i) {
   printf("%d %d", l_3, i);
}

GCC and GDB version (GCC commit id: 500d3f0a302):
$ gcc --version
gcc (GCC) 12.0.0 20211227 (experimental)
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gdb --version
GNU gdb (GDB) 11.2
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

GDB trace:
$ gcc -O2 -g a.c -o opt
$ gdb -q opt
Reading symbols from opt...
(gdb) b 6
Breakpoint 1 at 0x400410: /home/stepping/2/reduce/a.c:6. (2 locations)
(gdb) r
Starting program: /home/stepping/2/reduce/opt

Breakpoint 1, main () at a.c:6
6           test(l_3, i);
(gdb) info loc
No locals.

ASM at -O2:
0000000000400410 <main>:
 400410:       48 83 ec 08             sub    $0x8,%rsp
 400414:       be 08 00 00 00          mov    $0x8,%esi
 400419:       bf 05 00 00 00          mov    $0x5,%edi
 40041e:       31 c0                   xor    %eax,%eax
 400420:       e8 1b 01 00 00          callq  400540 <test>
 400425:       31 c0                   xor    %eax,%eax
 400427:       48 83 c4 08             add    $0x8,%rsp
 40042b:       c3                      retq    
 40042c:       0f 1f 40 00             nopl   0x0(%rax)

DWARF info at -O2:
0x00000065:     DW_TAG_inlined_subroutine
                 DW_AT_abstract_origin (0x000000ac "foo")
                 DW_AT_entry_pc        (0x0000000000400410)
                 DW_AT_unknown_2138    (0x02)
                 DW_AT_ranges  (0x0000000c
                    [0x0000000000400410, 0x0000000000400410)
                    [0x0000000000400414, 0x0000000000400425))
                 DW_AT_call_file       ("/home/stepping2/reduce/a.c")
                 DW_AT_call_line       (10)
                 DW_AT_call_column     (0x05)

0x0000007a:       DW_TAG_lexical_block
                   DW_AT_ranges        (0x0000000c
                      [0x0000000000400410, 0x0000000000400410)
                      [0x0000000000400414, 0x0000000000400425))

0x0000007f:         DW_TAG_variable
                     DW_AT_abstract_origin     (0x000000b9 "l_3")

0x00000084:         DW_TAG_variable
                     DW_AT_abstract_origin     (0x000000c3 "i")
                     DW_AT_location    (0x0000000e:  
                        [0x0000000000400410, 0x0000000000400410): DW_OP_lit0, DW_OP_stack_value)
                     DW_AT_unknown_2137        (0x0000000c)

0x00000091:         DW_TAG_call_site
                     DW_AT_call_return_pc      (0x0000000000400425)
                     DW_AT_call_origin (0x0000002a)

0x0000009e:           DW_TAG_call_site_parameter
                       DW_AT_location  (DW_OP_reg5 RDI)
                       DW_AT_call_value        (DW_OP_lit5)

0x000000a3:           DW_TAG_call_site_parameter
                       DW_AT_location  (DW_OP_reg4 RSI)
                       DW_AT_call_value        (DW_OP_lit8)

...

0x000000da:   DW_TAG_subprogram
               DW_AT_abstract_origin   (0x000000ac "foo")
               DW_AT_low_pc    (0x0000000000400520)
               DW_AT_high_pc   (0x0000000000400531)
               DW_AT_frame_base        (DW_OP_call_frame_cfa)
               DW_AT_call_all_calls    (true)

0x000000f1:     DW_TAG_variable
                 DW_AT_abstract_origin (0x000000b9 "l_3")

0x000000f6:     DW_TAG_variable
                 DW_AT_abstract_origin (0x000000c3 "i")
                 DW_AT_location        (0x0000001e:  
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit0, DW_OP_stack_value)
                 DW_AT_unknown_2137    (0x0000001c)

0x00000103:     DW_TAG_call_site
                 DW_AT_call_return_pc  (0x0000000000400531)
                 DW_AT_call_tail_call  (true)
                 DW_AT_call_origin     (0x0000002a)

0x00000110:       DW_TAG_call_site_parameter
                   DW_AT_location      (DW_OP_reg5 RDI)
                   DW_AT_call_value    (DW_OP_lit5)

0x00000115:       DW_TAG_call_site_parameter
                   DW_AT_location      (DW_OP_reg4 RSI)
                   DW_AT_call_value    (DW_OP_lit8)

From an initial assessment, there may be multiple factors at play behind the unavailability of variables i and l_3:
- the empty range in the location definition for variable i;
- the missing location definition for variable l_3 together with the missing const value attribute in the variable DIE in the subprogram DIE of function foo;
- the empty ranges in both the inlined subroutine location definition and the lexical block location definition nested in the inlined subroutine.

Through some testing we found out that the optimizations that make the variables disappear are related to inlining (-fearly-inlining, -finline, …) at both -O2 and -O3 but include also -ftree-dce at -O2 (since foo does not get inlined under -fno-tree-dce).

If we disable either inlining or -ftree-dce, variables l_3 and i appear in the current frame. However, a wrong value for i is displayed at the call site (0 instead of 8, see also the gdb bug report). When compiling with -fno-tree-dce lldb reports the correct value, while when disabling inlining it reports i as not available.


ASM with -fno-tree-dce at -O2:
0000000000400410 <main>:
 400410:       48 83 ec 08             sub    $0x8,%rsp
 400414:       31 c0                   xor    %eax,%eax
 400416:       e8 05 01 00 00          callq  400520 <foo>
 40041b:       31 c0                   xor    %eax,%eax
 40041d:       48 83 c4 08             add    $0x8,%rsp
 400421:       c3                      retq    
 400422:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 400429:       00 00 00  
 40042c:       0f 1f 40 00             nopl   0x0(%rax)

...

0000000000400520 <foo>:
 400520:       be 08 00 00 00          mov    $0x8,%esi
 400525:       bf 05 00 00 00          mov    $0x5,%edi
 40052a:       31 c0                   xor    %eax,%eax
 40052c:       e9 0f 00 00 00          jmpq   400540 <test>
 400531:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 400538:       00 00 00  
 40053b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

DEBUG info with -fno-tree-dce at -O2:
0x00000070:   DW_TAG_subprogram
               DW_AT_external  (true)
               DW_AT_name      ("foo")
               DW_AT_decl_file ("/home/stepping/2/reduce/a.c")
               DW_AT_decl_line (1)
               DW_AT_decl_column       (0x06)
               DW_AT_low_pc    (0x0000000000400520)
               DW_AT_high_pc   (0x0000000000400531)
               DW_AT_frame_base        (DW_OP_call_frame_cfa)
               DW_AT_call_all_calls    (true)

0x0000008a:     DW_TAG_variable
                 DW_AT_name    ("l_3")
                 DW_AT_decl_file       ("/home/stepping/2/reduce/a.c")
                 DW_AT_decl_line       (3)
                 DW_AT_decl_column     (0x09)
                 DW_AT_type    (0x00000039 "int")
                 DW_AT_const_value     (0x05)

0x00000097:     DW_TAG_variable
                 DW_AT_name    ("i")
                 DW_AT_decl_file       ("/home/stepping/2/reduce/a.c")
                 DW_AT_decl_line       (3)
                 DW_AT_decl_column     (0x12)
                 DW_AT_type    (0x00000039 "int")
                 DW_AT_location        (0x0000001e:  
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit0, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit1, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit2, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit3, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit4, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit5, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit6, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400520): DW_OP_lit7, DW_OP_stack_value
                    [0x0000000000400520, 0x0000000000400531): DW_OP_lit8, DW_OP_stack_value)
                 DW_AT_GNU_locviews    (0x0000000c)

We tested the program with older gcc versions. Within each version, the behavior at -O2 and -O3 is consistent: same generated DWARF info and debugging experience w.r.t. visible variables.  

gcc-6: both variables are visible and with correct values
ggc-7: both variables are visible with i's value being incorrect (not only in gdb, also lldb shows 0 instead of 8)
gcc-{8-9-10-11}: neither variable is visible (just like in the 500d3f0a302 git version that we tested)  

We are discussing gcc-7 below as we found some interesting details.

ASM from gcc-7 at -O2:
0000000000000560 <main>:
560:   48 83 ec 08             sub    $0x8,%rsp
564:   be 08 00 00 00          mov    $0x8,%esi
569:   bf 05 00 00 00          mov    $0x5,%edi
56e:   31 c0                   xor    %eax,%eax
570:   e8 3b 01 00 00          callq  6b0 <test>
575:   31 c0                   xor    %eax,%eax
577:   48 83 c4 08             add    $0x8,%rsp
57b:   c3                      retq    
57c:   0f 1f 40 00             nopl   0x0(%rax)

DWARF info generated for function foo:
DW_TAG_inlined_subroutine
                 DW_AT_abstract_origin (0x0000009e "foo")
                 DW_AT_low_pc  (0x0000000000000564)
                 DW_AT_high_pc (0x0000000000000575)
                 DW_AT_call_file       ("/home/stepping/2/reduce/a.c")
                 DW_AT_call_line       (10)

0x00000061:       DW_TAG_lexical_block
                   DW_AT_low_pc        (0x0000000000000564)
                   DW_AT_high_pc       (0x0000000000000575)

0x00000072:         DW_TAG_variable
                     DW_AT_abstract_origin     (0x000000e7 "l_3")


0x00000077:         DW_TAG_variable
                     DW_AT_abstract_origin     (0x000000ed "i")

0x0000007c:         DW_TAG_GNU_call_site
                     DW_AT_low_pc      (0x0000000000000575)
                     DW_AT_abstract_origin     (0x0000010c "test")



0x00000089:           DW_TAG_GNU_call_site_parameter
                       DW_AT_location  (DW_OP_reg5 RDI)
                       DW_AT_GNU_call_site_value       (DW_OP_lit5)

0x0000008e:           DW_TAG_GNU_call_site_parameter
                       DW_AT_location  (DW_OP_reg4 RSI)
                       DW_AT_GNU_call_site_value       (DW_OP_lit8)

...

0x000000cc:   DW_TAG_subprogram
               DW_AT_abstract_origin   (0x0000009e "foo")
               DW_AT_low_pc    (0x0000000000000690)
               DW_AT_high_pc   (0x00000000000006a1)
               DW_AT_frame_base        (DW_OP_call_frame_cfa)
               DW_AT_GNU_all_call_sites        (true)
               DW_AT_sibling   (0x0000010c)

0x000000e7:     DW_TAG_variable
                 DW_AT_abstract_origin (0x000000aa "l_3")
                 DW_AT_const_value     (0x05)

0x000000ed:     DW_TAG_variable
                 DW_AT_abstract_origin (0x000000b5 "i")
                 DW_AT_const_value     (0x00)

0x000000f3:     DW_TAG_GNU_call_site
                 DW_AT_low_pc  (0x00000000000006a1)
                 DW_AT_GNU_tail_call   (true)
                 DW_AT_abstract_origin (0x0000010c "test")

0x00000100:       DW_TAG_GNU_call_site_parameter
                   DW_AT_location      (DW_OP_reg5 RDI)
                   DW_AT_GNU_call_site_value   (DW_OP_lit5)

0x00000105:       DW_TAG_GNU_call_site_parameter
                   DW_AT_location      (DW_OP_reg4 RSI)
                   DW_AT_GNU_call_site_value   (DW_OP_lit8)

Differently from the latest gcc version that we tested, with gcc 7 in the subprogram DIE of the foo function there is the attribute const value defined for variable l_3 that makes the variable's value available. We can also see that there is the attribute const value defined for variable i that makes the variable have a wrong value during debugging.
Comment 1 Cristian Assaiante 2022-03-21 19:46:46 UTC
The gdb bug report can be found at: https://sourceware.org/bugzilla/show_bug.cgi?id=28987
Comment 2 Andrew Pinski 2022-03-21 19:56:18 UTC
Before cddce1:
  <bb 2> :
  # DEBUG BEGIN_STMT
  # DEBUG l_3 => 5
  # DEBUG i => 0
  # DEBUG BEGIN_STMT
  goto <bb 4>; [INV]

  <bb 3> :
  # DEBUG BEGIN_STMT
  i_6 = i_1 + 1;
  # DEBUG i => i_6

  <bb 4> :
  # i_1 = PHI <0(2), i_6(3)>
  # DEBUG i => i_1
  # DEBUG BEGIN_STMT
  if (i_1 != 8)
    goto <bb 3>; [INV]
  else
    goto <bb 5>; [INV]

  <bb 5> :
  # DEBUG BEGIN_STMT
  test (5, 8);

After:
  <bb 2> [local count: 1073741824]:
  # DEBUG BEGIN_STMT
  # DEBUG l_3 => 5
  # DEBUG i => 0
  # DEBUG BEGIN_STMT
  # DEBUG i => NULL
  # DEBUG BEGIN_STMT
  # DEBUG BEGIN_STMT
  test (5, 8); [tail call]


Note CCP is able to figure out l_3 => 5 piece. When VRP figures out i => 8 and changes "i_1 <= 7" into "i_1 != 8", it does not add a debug statement for i => 8 after the branch (thinking it does not need one, I don't think it needs one but CDDEC should be fixed such that it change i => NULL to 8 I think ....)
Comment 3 Richard Biener 2022-03-22 08:41:15 UTC
(In reply to Andrew Pinski from comment #2)
> Before cddce1:
>   <bb 2> :
>   # DEBUG BEGIN_STMT
>   # DEBUG l_3 => 5
>   # DEBUG i => 0
>   # DEBUG BEGIN_STMT
>   goto <bb 4>; [INV]
> 
>   <bb 3> :
>   # DEBUG BEGIN_STMT
>   i_6 = i_1 + 1;
>   # DEBUG i => i_6
> 
>   <bb 4> :
>   # i_1 = PHI <0(2), i_6(3)>
>   # DEBUG i => i_1
>   # DEBUG BEGIN_STMT
>   if (i_1 != 8)
>     goto <bb 3>; [INV]
>   else
>     goto <bb 5>; [INV]
> 
>   <bb 5> :
>   # DEBUG BEGIN_STMT
>   test (5, 8);
> 
> After:
>   <bb 2> [local count: 1073741824]:
>   # DEBUG BEGIN_STMT
>   # DEBUG l_3 => 5
>   # DEBUG i => 0
>   # DEBUG BEGIN_STMT
>   # DEBUG i => NULL
>   # DEBUG BEGIN_STMT
>   # DEBUG BEGIN_STMT
>   test (5, 8); [tail call]
> 
> 
> Note CCP is able to figure out l_3 => 5 piece. When VRP figures out i => 8
> and changes "i_1 <= 7" into "i_1 != 8", it does not add a debug statement
> for i => 8 after the branch (thinking it does not need one, I don't think it
> needs one but CDDEC should be fixed such that it change i => NULL to 8 I
> think ....)

Passes usually do not try to be too clever in creating debuginfo, in this
case inserting a debug-bind with a loop final value.  Instead CDDCE does
it correctly and resets 'i' from the initial value.

  <bb 2> :
  [t.c:3:4] # DEBUG BEGIN_STMT
  [t.c:3:8] # DEBUG l_3 => 5
  [t.c:3:17] # DEBUG i => 0
  [t.c:4:4] # DEBUG BEGIN_STMT
  # DEBUG i => NULL
  [t.c:4:13] # DEBUG BEGIN_STMT
  [t.c:6:4] # DEBUG BEGIN_STMT
  [t.c:6:4] test (5, 8);
  [t.c:7:1] return;

I think that's exactly OK.  What's the thing to improve is EVRP which does

 void foo ()
 {
   int i;
@@ -37,14 +58,14 @@
   # i_1 = PHI <[t.c:3:17] 0(2), [t.c:4:19] i_6(3)>
   # DEBUG i => i_1
   [t.c:4:13] # DEBUG BEGIN_STMT
-  [t.c:4:13] if (i_1 <= 7)
+  [t.c:4:13] if (i_1 != 8)
     goto <bb 3>; [INV]
   else
     goto <bb 5>; [INV]
 
   <bb 5> :
   [t.c:6:4] # DEBUG BEGIN_STMT
-  [t.c:6:4] test (5, i_1);
+  [t.c:6:4] test (5, 8);
   [t.c:7:1] return;

as you say, but when propagating a constant it should make sure to insert
a debug stmt at the definition it removes (it doesn't remove any - but
in principle it adds i_42 = 8; in bb5 and updates SSA form to use the new
name in dominating stmts).  This isn't an issue with CCP which doesn't
have contextual lattices, so there's always a definition that is later
replaced with a debug stmt.

The substitute-and-fold machinery could in principle be taught to do this,
but it would need to know whether it faces a "contextual" value (constant
or SSA name) or if there's an actual definition with the propagated value.

Not sure why this should be classified as wrong-debug - the debug info is
conservatively correct.  It's just lacking ...