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

Frequent use of line 0 in debug info reduces effectiveness of Cachegrind #65487

Open
nnethercote opened this issue Oct 17, 2019 · 18 comments
Open
Labels
A-debuginfo Area: Debugging information in compiled programs (DWARF, PDB, etc.) A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. S-waiting-on-LLVM Status: the compiler-dragon is eepy, can someone get it some tea? T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@nnethercote
Copy link
Contributor

I frequently use Cachegrind (and Callgrind) to profile rustc and other Rust programs. Cachegrind attributes instruction counts (and possibly cache misses and branch mispredictions) to specific lines of code, like this:

      .               /// Maps a string to its interned representation.
320,664 ( 0.27%)      pub fn intern(string: &str) -> Self {
160,332 ( 0.14%)          with_interner(|interner| interner.intern(string))
267,220 ( 0.23%)      }

But when profiling rustc with Cachegrind, lots of instruction counts don't get attribute to a particular line, so we end up with output like this:

1,555,039 ( 1.33%)  <counts for unidentified lines in /home/njn/.cargo/registry/
src/github.com-1ecc6299db9ec823/hashbrown-0.6.1/src/raw/mod.rs>

Often the fraction of executed instructions that don't get attributed to a particular line is 20%, 30%, or higher. That's a lot!

The way Cachegrind works is that the first time an instruction, X, from the binary is executed, Cachegrind gets X's file name and line number from debug info, and X's function name from the symbol table, and creates a (address, filename, fn_name, line_num) cost centre. Every time X is executed the cost centre is incremented appropriately.

The problem with the Rust code's debug info is that there are lots of instructions for which the debug info says the line number is 0. That means Cachegrind can't attribute a line, and all executions of such instructions count towards the "unidentified lines" entry. Notably, these instructions do have a valid filename.

I grabbed some debugging output from Valgrind and matched it up with the binary code, as produced by objdump -d. What follows is for the function syntax_pos::symbol::Interner::intern. The machine code is on the left, and Valgrind's output for each instruction (its runtime address, filename, and line number) is on the right.

0000000001b742a0 <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE>:
 1b742a0:  push   %rbp             # 0x65CF2A0 libsyntax_pos/symbol.rs:976
 1b742a1:  push   %r15             # 0x65CF2A1 libsyntax_pos/symbol.rs:976
 1b742a3:  push   %r14             # 0x65CF2A3 libsyntax_pos/symbol.rs:976
 1b742a5:  push   %r13             # 0x65CF2A5 libsyntax_pos/symbol.rs:976
 1b742a7:  push   %r12             # 0x65CF2A7 libsyntax_pos/symbol.rs:976
 1b742a9:  push   %rbx             # 0x65CF2A9 libsyntax_pos/symbol.rs:976
 1b742aa:  sub    $0x58,%rsp       # 0x65CF2AA libsyntax_pos/symbol.rs:976
 1b742ae:  mov    %rsi,%r13        # 0x65CF2AE libsyntax_pos/symbol.rs:976
 1b742b1:  movabs $0x517cc1b727220a95,%rax
                                   # 0x65CF2B1 libsyntax_pos/symbol.rs:976
 1b742bb:  cmp    $0x8,%rdx        # 0x65CF2BB rustc-hash-1.0.1/src/lib.rs:81
 1b742bf:  jb     1b742e7 <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0x47>
                                   # 0x65CF2BF rustc-hash-1.0.1/src/lib.rs:81
 1b742c1:  lea    -0x8(%rdx),%r8   # 0x65CF2C1 rustc-hash-1.0.1/src/lib.rs:81
 1b742c5:  mov    %r8,%rbp         # 0x65CF2C5 rustc-hash-1.0.1/src/lib.rs:81
 1b742c8:  shr    $0x3,%rbp        # 0x65CF2C8 rustc-hash-1.0.1/src/lib.rs:81
 1b742cc:  add    $0x1,%rbp        # 0x65CF2CC rustc-hash-1.0.1/src/lib.rs:81
 1b742d0:  mov    %ebp,%ecx        # 0x65CF2D0 rustc-hash-1.0.1/src/lib.rs:81
 1b742d2:  and    $0x3,%ecx        # 0x65CF2D2 rustc-hash-1.0.1/src/lib.rs:81
 1b742d5:  cmp    $0x18,%r8        # 0x65CF2D5 rustc-hash-1.0.1/src/lib.rs:81
 1b742d9:  jae    1b742fe <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0x5e>
                                   # 0x65CF2D9 rustc-hash-1.0.1/src/lib.rs:81
 1b742db:  xor    %ebx,%ebx        # 0x65CF2DB rustc-hash-1.0.1/src/lib.rs:0
 1b742dd:  mov    %r13,%rsi        # 0x65CF2DD rustc-hash-1.0.1/src/lib.rs:0
 1b742e0:  test   %rcx,%rcx        # 0x65CF2E0 rustc-hash-1.0.1/src/lib.rs:81
 1b742e3:  jne    1b7434e <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0xae>(0x65CF2E3) -> 1b7434e
                                   # 0x65CF2E3 rustc-hash-1.0.1/src/lib.rs:81
 ...                               # ...
 1b7434e:  xor    %ebp,%ebp        # 0x65CF34E rustc-hash-1.0.1/src/lib.rs:0
 1b74350:  rol    $0x5,%rbx        # 0x65CF350 libcore/num/mod.rs:2461
 1b74354:  xor    (%rsi,%rbp,8),%rbx 
                                   # 0x65CF354 libcore/ops/bit.rs:306
 1b74358:  imul   %rax,%rbx        # 0x65CF358 libcore/num/mod.rs:3096
 1b7435c:  add    $0x1,%rbp        # 0x65CF35C rustc-hash-1.0.1/src/lib.rs:81
 1b74360:  cmp    %rbp,%rcx        # 0x65CF360 rustc-hash-1.0.1/src/lib.rs:81
 1b74363:  jne    1b74350 <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0xb0>
                                   # 0x65CF363 rustc-hash-1.0.1/src/lib.rs:81
 1b74365:  mov    %r8,%rsi         # 0x65CF365 rustc-hash-1.0.1/src/lib.rs:81
 1b74368:  and    $0xfffffffffffffff8,%rsi
                                   # 0x65CF368 rustc-hash-1.0.1/src/lib.rs:81
 1b7436c:  lea    (%rsi,%r13,1),%rcx
                                   # 0x65CF36C rustc-hash-1.0.1/src/lib.rs:81
 1b74370:  add    $0x8,%rcx        # 0x65CF370 rustc-hash-1.0.1/src/lib.rs:81
 1b74374:  sub    %rsi,%r8         # 0x65CF374 rustc-hash-1.0.1/src/lib.rs:81
 1b74377:  cmp    $0x3,%r8         # 0x65CF377 rustc-hash-1.0.1/src/lib.rs:85
 1b7437b:  jbe    1b74392 <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0xf2>
                                   # 0x65CF37B rustc-hash-1.0.1/src/lib.rs:85
 ...                               # ...
 1b74392:  cmp    $0x2,%r8         # 0x65CF392 rustc-hash-1.0.1/src/lib.rs:89
 1b74396:  jae    1b7458b <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0x2eb>
                                   # 0x65CF396 rustc-hash-1.0.1/src/lib.rs:89
 1b7439c:  test   %r8,%r8          # 0x65CF39C rustc-hash-1.0.1/src/lib.rs:93
 1b7439f:  je     1b743af <_ZN10syntax_pos6symbol8Interner6intern17h6c86044010aa5bfcE+0x10f>
                                   # 0x65CF39F rustc-hash-1.0.1/src/lib.rs:93
 1b743a1:  movzbl (%rcx),%ecx      # 0x65CF3A1 rustc-hash-1.0.1/src/lib.rs:94
 1b743a4:  rol    $0x5,%rbx        # 0x65CF3A4 libcore/num/mod.rs:2461
 1b743a8:  xor    %rcx,%rbx        # 0x65CF3A8 libcore/ops/bit.rs:306
 1b743ab:  imul   %rax,%rbx        # 0x65CF3AB libcore/num/mod.rs:3096
 1b743af:  lea    0x30(%rdi),%rcx  # 0x65CF3AF libsyntax_pos/symbol.rs:0
 1b743b3:  mov    %rcx,0x20(%rsp)
                                   # 0x65CF3B3 libsyntax_pos/symbol.rs:0
 1b743b8:  rol    $0x5,%rbx        # 0x65CF3B8 libcore/num/mod.rs:2461
 1b743bc:  xor    $0xff,%rbx       # 0x65CF3BC libcore/ops/bit.rs:306
 1b743c3:  imul   %rax,%rbx        # 0x65CF3C3 libcore/num/mod.rs:3096
 1b743c7:  mov    0x30(%rdi),%rcx
                                   # 0x65CF3C7 hashbrown-0.6.1/src/raw/mod.rs:489
 1b743cb:  mov    0x38(%rdi),%rsi  # 0x65CF3CB hashbrown-0.6.1/src/raw/mod.rs:0
 1b743cf:  mov    %rbx,%rax        # 0x65CF3CF hashbrown-0.6.1/src/raw/mod.rs:0
 1b743d2:  shr    $0x39,%rax       # 0x65CF3D2 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743d6:  movd   %eax,%xmm0       # 0x65CF3D6 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743da:  punpcklbw %xmm0,%xmm0   # 0x65CF3DA hashbrown-0.6.1/src/raw/mod.rs:0
 1b743de:  pshuflw $0xe0,%xmm0,%xmm0
                                   # 0x65CF3DE hashbrown-0.6.1/src/raw/mod.rs:0
 1b743e3:  pshufd $0x0,%xmm0,%xmm1 # 0x65CF3E3 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743e8:  mov    %rdi,0x28(%rsp)  # 0x65CF3E8 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743ed:  mov    0x40(%rdi),%r12  # 0x65CF3ED hashbrown-0.6.1/src/raw/mod.rs:0
 1b743f1:  xor    %ebp,%ebp        # 0x65CF3F1 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743f3:  pcmpeqd %xmm2,%xmm2     # 0x65CF3F3 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743f7:  mov    0x4a9b8a(%rip),%r14        # 201df88 <bcmp@GLIBC_2.2.5>
                                   # 0x65CF3F7 hashbrown-0.6.1/src/raw/mod.rs:0
 1b743fe:  and    %rcx,%rbx        # 0x65CF3FE hashbrown-0.6.1/src/raw/mod.rs:0
 1b74401:  movdqu (%rsi,%rbx,1),%xmm3
                                   # 0x65CF401 libcore/intrinsics.rs:1462

The thing to notice is that the majority of the lines have a valid filename and line number. (The filenames jump around a lot due to inlining. That's fine.) But quite a few of them have a line number of zero. There is no obvious pattern to the zeroes; sometimes there are one or two in a sequence, sometimes there are more.

If we trace through these instructions once, here are the filename/linenum pairs that get instructions counted towards them:

libsyntax_pos/symbol.rs:976        x 9
rustc-hash-1.0.1/src/lib.rs:81     x 10
rustc-hash-1.0.1/src/lib.rs:0              (0!)
rustc-hash-1.0.1/src/lib.rs:81     x 2
rustc-hash-1.0.1/src/lib.rs:0              (0!)
libcore/num/mod.rs:2461
libcore/ops/bit.rs:306
libcore/num/mod.rs:3096
rustc-hash-1.0.1/src/lib.rs:81     x 8
rustc-hash-1.0.1/src/lib.rs:85     x 2
rustc-hash-1.0.1/src/lib.rs:89     x 2
rustc-hash-1.0.1/src/lib.rs:93     x 2
rustc-hash-1.0.1/src/lib.rs:94
libcore/num/mod.rs:2461
libcore/ops/bit.rs:306
libcore/num/mod.rs:3096
libsyntax_pos/symbol.rs:0          x 2     (0!)
libcore/num/mod.rs:2461
libcore/ops/bit.rs:306
libcore/num/mod.rs:3096
hashbrown-0.6.1/src/raw/mod.rs:489
hashbrown-0.6.1/src/raw/mod.rs:0   x 13    (0!)
libcore/intrinsics.rs:1462

47 instructions have a non-zero line number, and 17 have a zero line number.

I haven't seen this problem occur with C and C++ code.

I suspect the problem is that the production of debug info isn't rigorous enough in some fashion, and Cachegrind's requirements might be more onerous than other tools'. (Debug info is often not tested thoroughly.) Sometimes it can be non-obvious exactly which line of code an instruction should be attributed to. In that case, I'm not too fussed so long as it's attributed to something plausible and not zero.

cc @julian-seward1

@jonas-schievink jonas-schievink added A-debuginfo Area: Debugging information in compiled programs (DWARF, PDB, etc.) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 17, 2019
@nnethercote
Copy link
Contributor Author

@michaelwoerister: Do you know anything about this? If you could point to me where debug info is produced in rustc, that would be helpful. Thanks.

@michaelwoerister
Copy link
Member

It looks like setting debug locations for instructions always goes through set_debug_location:

pub fn set_debug_location(

I don't know where the zeros are introduced though. It might have to do with macros.

@michaelwoerister
Copy link
Member

And set_debug_location() is transitively called from set_debug_loc when building LLVM IR from MIR, where the span in question comes from a mir::SourceInfo:

pub fn set_debug_loc(
&mut self,
bx: &mut Bx,
source_info: mir::SourceInfo
) {
let (scope, span) = self.debug_loc(source_info);
bx.set_source_location(&mut self.debug_context, scope, span);
}

I suspect that those mir::SourceInfo values already contain the zero lines (but it would be good to verify).

@mati865

This comment has been minimized.

@est31

This comment was marked as outdated.

@mati865

This comment was marked as outdated.

@est31
Copy link
Member

est31 commented Oct 26, 2019

I suspect that those mir::SourceInfo values already contain the zero lines (but it would be good to verify).

@michaelwoerister I've tried verifying it on the playground with a non-trivial rosetta code example and the "show MIR" feature, but it doesn't create any line number zero spans: mir.txt
Grepping that file for ".rs:0" gives no results. When I change the playground to output LLVM IR and search for !DILocation(line: 0, it doesn't show anything either. So I chose to dig a bit further and inspect the LLVM IR. I grepped both for omitted lines via | rg "DILocation" | rg -v line as well as line: 0 but got nothing each time. When I grepped for debuginfo lines (those that start with !) that do contain a file name that isn't <unknown> but don't contain a line number, I only got DIFile and DILexicalBlockFile types (command used (!2 is the <unknown> file): | rg "^!" | rg -v line | rg file | rg -v 'file: !2').

Now to those DILexicalBlockFile types. It seems that they all are mentioned from a DILocation as scope, like the !1227 one below (excerpt):

!1215 = distinct !DISubprogram(name: "copy_nonoverlapping<hashbrown::raw::RawTable<(alloc::string::String, i32)>>", linkageName: "_ZN4core10intrinsics19copy_nonoverlapping17h6368a154fa483cbbE", scope: !1185, file: !1184, line: 1454, type: !1216, scopeLine: 1454, flags: DIFlagPrototyped, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !139, templateParams: !1220, retainedNodes: !4)
!1226 = !DILocation(line: 184, column: 22, scope: !1227)
!1227 = !DILexicalBlockFile(scope: !1215, file: !1198, discriminator: 0)
!1230 = !DILocation(line: 184, column: 58, scope: !1227)
!1231 = !DILocation(line: 12, column: 8, scope: !1227)

On the rustc side, they are generated by the LLVMRustDIBuilderCreateLexicalBlockFile function whose only transitive caller is the scope_metadata_for_loc function in this snippet:

pub fn debug_loc(&self, source_info: mir::SourceInfo) -> (Option<Bx::DIScope>, Span) {
// Bail out if debug info emission is not enabled.
match self.debug_context {
FunctionDebugContext::DebugInfoDisabled |
FunctionDebugContext::FunctionWithoutDebugInfo => {
return (self.scopes[source_info.scope].scope_metadata, source_info.span);
}
FunctionDebugContext::RegularContext(_) =>{}
}
// In order to have a good line stepping behavior in debugger, we overwrite debug
// locations of macro expansions with that of the outermost expansion site
// (unless the crate is being compiled with `-Z debug-macros`).
if !source_info.span.from_expansion() ||
self.cx.sess().opts.debugging_opts.debug_macros {
let scope = self.scope_metadata_for_loc(source_info.scope, source_info.span.lo());
(scope, source_info.span)
} else {
// Walk up the macro expansion chain until we reach a non-expanded span.
// We also stop at the function body level because no line stepping can occur
// at the level above that.
let span = syntax_pos::hygiene::walk_chain(source_info.span, self.mir.span.ctxt());
let scope = self.scope_metadata_for_loc(source_info.scope, span.lo());
// Use span of the outermost expansion site, while keeping the original lexical scope.
(scope, span)
}
}
// DILocations inherit source file name from the parent DIScope. Due to macro expansions
// it may so happen that the current span belongs to a different file than the DIScope
// corresponding to span's containing source scope. If so, we need to create a DIScope
// "extension" into that file.
fn scope_metadata_for_loc(&self, scope_id: mir::SourceScope, pos: BytePos)
-> Option<Bx::DIScope> {
let scope_metadata = self.scopes[scope_id].scope_metadata;
if pos < self.scopes[scope_id].file_start_pos ||
pos >= self.scopes[scope_id].file_end_pos {
let sm = self.cx.sess().source_map();
let defining_crate = self.debug_context.get_ref(DUMMY_SP).defining_crate;
Some(self.cx.extend_scope_to_file(
scope_metadata.unwrap(),
&sm.lookup_char_pos(pos).file,
defining_crate
))
} else {
scope_metadata
}
}

The comments point to macros as the possible cause, but when I get back to @nnethercote 's original report, hashbrown's raw/mod.rs doesn't contain any macro declarations, nor does it contain any major macro invocations beyond one cfg_if, one derive on an error, and a few debug_assert invocations.

So idk. Maybe that's where those 0 line numbers are coming from. However, while I'm not 100% sure, it seems that DILexicalBlockFiles are only used as scope for DILocations that do have line numbers, which would be an argument against DILexicalBlockFile being the culprit. No idea.

I'd have loved to investigate further but I was unable to generate @nnethercote 's output. Which objdump parameters did you use to print the line numbers next to the assembly?

@nnethercote
Copy link
Contributor Author

@est31: the text I showed above was hand-made, combining objdump -d output with some custom debugging output I inserted into Valgrind. So you won't be able to recreate it, sorry :(

@Tiwalun
Copy link
Contributor

Tiwalun commented Nov 6, 2019

I've had a similar problem with some embedded code, for the thumbv6m-none-eabi target.
I've created a small sample: https://github.com/Tiwalun/rust-debuginfo-test , and uploaded the MIR, LLVM IR, the generated assembly, and the debug information: https://gist.github.com/Tiwalun/840a6792f23e8ab34cbeff2f3a7b04fa .

In my case, the 0 line numbers only appear in the final debug info, the LLVM IR and MIR do not seem to contain them.

Are there rustc / llvm flags to inspect how the debug information is created from the IR?

@nnethercote
Copy link
Contributor Author

@Tiwalun: that's very helpful, thank you. Can you show the commands you used to generate those outputs?

@est31
Copy link
Member

est31 commented Nov 7, 2019

@Tiwalun can you expand on where those 0 line numbers are to be found? I can't find them anywhere in your gist.

@nnethercote
Copy link
Contributor Author

@est31: in debuginfo_test.dwarfdump there is:

Address            Line   Column File   ISA Discriminator Flags
------------------ ------ ------ ------ --- ------------- -------------
0x00000000000000c0     17      0      1   0             0  is_stmt
0x00000000000000c8     18      4      1   0             0  is_stmt prologue_end
0x00000000000000d4     19      1      1   0             0  is_stmt
0x00000000000000d8     18      4      1   0             0  is_stmt
0x00000000000000e0      0      4      1   0             0 
0x00000000000000e4      0      4      1   0             0  end_sequence
0x00000000000000e4      8      0      1   0             0  is_stmt
0x00000000000000e4     11      4      1   0             0  is_stmt prologue_end
0x00000000000000e6      0      4      1   0             0 
0x00000000000000ea     12      8      1   0             0  is_stmt
0x00000000000000f0     11      4      1   0             0  is_stmt
0x00000000000000f2     11      4      1   0             0  is_stmt end_sequence

Three of the entries have a 0 value for line.

@nnethercote
Copy link
Contributor Author

nnethercote commented Nov 7, 2019

It's interesting that 0xe4 has three entries, with different line/column combinations:

line=0, column=4
line=8, column=0
line=11, column=4 (corresponds to the `loop` keyword)

The instruction at 0xe4 is a branch, so it seems to correspond to the loop. Line 8 has the #[entry] annotation on main.

@nnethercote
Copy link
Contributor Author

The line=8, column=0 location corresponds to this LLVM IR line, describing the scope of main:

!14 = distinct !DISubprogram(name: "b69446274u81851d", linkageName: "main", scope: !5, file: !1, line: 8, type: !15, scopeLine: 8, flags: DIFlagPrototyped | DIFlagNoReturn, spFlags: DISPFlagDefinition, unit: !0, templateParams: !2, retainedNodes: !2)

The line=11, column=4 location corresponds to this LLVM IR line, describing loop:

!18 = !DILocation(line: 11, column: 4, scope: !14)

The line=0, column=4 entry doesn't correpond to anything in the LLVM IR that I can see.

@est31
Copy link
Member

est31 commented Nov 7, 2019

There are three entries for the same instruction: [0, 4], [8,0] and [11,4]. @nnethercote identified the origin of the [8,0] and [11,4] entries. I modified @Tiwalun 's code to find out where the [0,4] came from. I added some spaces.

#[entry]
fn main() -> ! {

                    loop {
        delay(1000);
      }
}


fn delay(ms: u16) {
          ms + 1;
}

I got this result from dwarfdump -a (showing the relevant excerpt):

<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
[...]
0x000000c8  [  18,10] NS PE
0x000000d4  [  19, 1] NS
0x000000d8  [  18,10] NS
0x000000e0  [   0,10]
0x000000e4  [   0,10] ET
0x000000e4  [   8, 0] NS
0x000000e4  [  11,20] NS PE
0x000000e6  [   0,20]
0x000000ea  [  12, 8] NS
0x000000f0  [  11,20] NS
0x000000f2  [  11,20] NS ET

Now it became [0, 10], [8,0] and [11,20]. There is only one line in Rust that starts at column 10, which is the ms + 1. This listing is very interesting as main only starts at address e4, the code before belongs to the delay function. So the e0 directly above that has a [0, 10] as well already belongs to the delay function, as does the d8.

So somehow the debuginfo leaks over. In the LLVM IR I can't see any usage of anything with column 10 in the main function. It's all only in the delay function.

To summarize I'd say there is a bug in LLVM involved.

@jonas-schievink jonas-schievink added the A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. label Apr 5, 2020
@cassaundra
Copy link
Contributor

Interestingly, I believe I was able to track this down to a regression in nightly-2017-04-26 (with some certainty, I have not been able to compile rustc from back then). From that day, #41508 seems like the most likely culprit. It's rather plausible that one of the many changes in that PR altered how debuginfo contexts were managed in such a way that certain lines get lost in the process.

I've also noticed that pretty much all of these 0-lines occur near scope boundaries, seemingly in the middle of drop glue code. I don't have a ton of time to work on this, but I would definitely recommend starting there for anyone that takes a look.

@cassaundra
Copy link
Contributor

cassaundra commented Jun 14, 2023

Regarding the way that the 0-lines cause columns to leak over, that likely has to do with the way line/column information is kept track of internally by LLVM. Line and column changes are emitted as DW_LNS_advance_line and DW_LNS_set_column opcodes respectively, more or less independent of one another. Whether or not it's a bug to not reset the column to 0 when the line is set to 0, I don't know, but as far as I can tell it's an unrelated issue.

@wesleywiser
Copy link
Member

I ran into this issue while looking at the stack trace of a core dump:

fn main() {
    never_returns("this is the panic message");
}

struct HasDrop(&'static str);
impl Drop for HasDrop {
    fn drop(&mut self) { }
}
struct HasDropGlue(HasDrop);

#[no_mangle]
pub fn never_returns(s: &'static str) -> ! {
    (move || {
        if let Some(s) = condition(s) {
            do_panic(&HasDropGlue(HasDrop(s)));
        } else {
            do_panic(&HasDropGlue(HasDrop("unknown"))); // this line will have a 0 line number
        }
    })()
}

#[inline(never)]
fn condition(s: &str) -> Option<&str> {
    if s.len() == 0 { Some("hello") }
    else { None }
}

fn do_panic(h: &HasDropGlue) -> ! {
    panic!("{}", h.0.0);
}

eg, in lldb (frame 15):

$ rustc --crate-name panic_test2 repro.rs -C opt-level=1 -C panic=abort -C debuginfo=1
$ lldb ./panic_test2
(lldb) r
Process 30942 launched: '/tmp/panic_test/panic_test2' (x86_64)
thread 'main' panicked at src/main.rs:29:5:
unknown
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Process 30942 stopped
* thread #1, name = 'panic_test2', stop reason = signal SIGABRT
    frame #0: 0x00007ffff7e01a7c libc.so.6`__GI___pthread_kill at pthread_kill.c:44:76

(lldb) bt
* thread #1, name = 'panic_test2', stop reason = signal SIGABRT
...
 frame #14: 0x000055555555c5c6 panic_test2`panic_test2::do_panic::h2c0ead66774c0583 at main.rs:29:5
 frame #15: 0x000055555555c591 panic_test2`panic_test2::never_returns::_$u7b$$u7b$closure$u7d$$u7d$::h2a38757633d1adfc at main.rs:0:23
 frame #16: 0x000055555555c548 panic_test2`never_returns at main.rs:13:5
 frame #17: 0x000055555555c522 panic_test2`panic_test2::main::h426c9076b0e597ab at main.rs:2:5
...

Which matches what is in the line table:

$ llvm-dwarfdump --debug-line ./panic_test2
...
file_names[  8]:
           name: "main.rs"
      dir_index: 7
       mod_time: 0x00000000
         length: 0x00000000
...
Address            Line   Column File   ISA Discriminator Flags
------------------ ------ ------ ------ --- ------------- -------------
...
0x0000000000008587      0     23      8   0             0
0x0000000000008593      0     23      8   0             0  end_sequence

rustc generates the correct line numbers for this program:

...
bb2:                                              ; preds = %start
...
; call panic_test2::do_panic
  call fastcc void @_ZN11panic_test28do_panic17h2c0ead66774c0583E(ptr noalias noundef nonnull readonly align 8 dereferenceable(16) %_7) #10, !dbg !92
...

bb3:                                              ; preds = %start
...
; call panic_test2::do_panic
  call fastcc void @_ZN11panic_test28do_panic17h2c0ead66774c0583E(ptr noalias noundef nonnull readonly align 8 dereferenceable(16) %_11) #10, !dbg !94
...
!85 = distinct !DISubprogram(name: "bad_debug_info", linkageName: "bad_debug_info", scope: !86, file: !80, line: 14, type: !13, scopeLine: 14, flags: DIFlagPrototyped | DIFlagNoReturn, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !6, templateParams: !14)
!88 = distinct !DILexicalBlock(scope: !85, file: !80, line: 15, column: 39)
!92 = !DILocation(line: 16, column: 13, scope: !88)
!94 = !DILocation(line: 18, column: 13, scope: !85)

However, when lowering to machine code, these line numbers are erased and replaced with line 0:

...
        .loc    1 0 23 is_stmt 0                # src/main.rs:0:23
        leaq    8(%rsp), %rdi
        callq   panic_test2::do_panic::h2c0ead66774c0583
        ud2
...

I've filed an upstream issue for this llvm/llvm-project#65667

@workingjubilee workingjubilee added S-blocked Status: Blocked on something else such as an RFC or other implementation work. S-waiting-on-LLVM Status: the compiler-dragon is eepy, can someone get it some tea? and removed S-blocked Status: Blocked on something else such as an RFC or other implementation work. labels May 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-debuginfo Area: Debugging information in compiled programs (DWARF, PDB, etc.) A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. S-waiting-on-LLVM Status: the compiler-dragon is eepy, can someone get it some tea? T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants