DW_TAG_formal_parameter always has DW_AT_decl_line == 1

d457949
Opened by Josh Stone at 2022-07-18 14:24:20

It seems that Rust debuginfo always writes decl_line of 1 for formal parameters.

With this line.rs:

#![crate_type = "dylib"] // line 1
pub fn foo( // 2
    x: i32, // 3
    y: i32) // 4
    -> i32  // 5
{ x + y }   // 6

With:

$ rustc +nightly -Vv
rustc 1.22.0-nightly (185cc5f26 2017-10-02)
binary: rustc
commit-hash: 185cc5f26d2c8a794189b028b43f6a3b8fc586db
commit-date: 2017-10-02
host: x86_64-unknown-linux-gnu
release: 1.22.0-nightly
LLVM version: 4.0

I get this output:

$ rustc +nightly -g line.rs
$ dwgrep 'entry ?(@AT_name == "foo") child*' libline.so
[2f]    subprogram
        low_pc  0x42100
        high_pc 66
        frame_base      0..0xffffffffffffffff:0 reg6
        linkage_name    "_ZN4line3fooE"
        name    "foo"
        decl_file       "/tmp/line.rs"
        decl_line       2
        type    [8c] base_type
        external        true
[4c]    formal_parameter
        location        0..0xffffffffffffffff:0 fbreg <-16>
        name    "x"
        decl_file       "/tmp/line.rs"
        decl_line       1
        type    [8c] base_type
[5a]    formal_parameter
        location        0..0xffffffffffffffff:0 fbreg <-12>
        name    "y"
        decl_file       "/tmp/line.rs"
        decl_line       1
        type    [8c] base_type
[68]    lexical_block
        ranges  0x4210e..0x4212a, 0x4213a..0x42142
[6d]    variable
        location        0..0xffffffffffffffff:0 fbreg <-8>
        name    "x"
        decl_file       "/tmp/line.rs"
        decl_line       3
        type    [8c] base_type
[7b]    variable
        location        0..0xffffffffffffffff:0 fbreg <-4>
        name    "y"
        decl_file       "/tmp/line.rs"
        decl_line       4
        type    [8c] base_type

So it describes the formal parameters of x and y both with line 1, but also describes local variables with the correct lines 3 and 4.

In LLVM IR it looks like this, with the wrong lines in !10 and !13:

$ rustc +nightly -g line.rs --emit=llvm-ir
$ sed -n '/DIComp/,$p' line.ll
!0 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !1, producer: "clang LLVM (rustc version 1.22.0-nightly (185cc5f26 2017-10-02))", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
!1 = !DIFile(filename: "line.rs", directory: "/tmp")
!2 = !{}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!4 = distinct !DISubprogram(name: "foo", linkageName: "_ZN4line3fooE", scope: !5, file: !1, line: 2, type: !7, isLocal: false, isDefinition: true, scopeLine: 2, flags: DIFlagPrototyped, isOptimized: false, unit: !0, templateParams: !2, variables: !2)
!5 = !DINamespace(name: "line", scope: null, file: !6)
!6 = !DIFile(filename: "<unknown>", directory: "")
!7 = !DISubroutineType(types: !8)
!8 = !{!9, !9, !9}
!9 = !DIBasicType(name: "i32", size: 32, encoding: DW_ATE_signed)
!10 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !1, line: 1, type: !9)
!11 = !DIExpression()
!12 = !DILocation(line: 1, scope: !4)
!13 = !DILocalVariable(name: "y", arg: 2, scope: !4, file: !1, line: 1, type: !9)
!14 = !DILocalVariable(name: "x", scope: !15, file: !1, line: 3, type: !9, align: 4)
!15 = distinct !DILexicalBlock(scope: !4, file: !1, line: 6)
!16 = !DILocation(line: 3, scope: !15)
!17 = !DILocalVariable(name: "y", scope: !15, file: !1, line: 4, type: !9, align: 4)
!18 = !DILocation(line: 4, scope: !15)
!19 = !DILocation(line: 6, scope: !15)
!20 = !DILocation(line: 6, scope: !4)
  1. Historically, 1.11.0 looked more like I would expect, with the right line numbers on the formal parameters and no extra local variables.

    $ rustc +1.11.0 -g line.rs --emit=llvm-ir && sed -n '/DIComp/,$p' line.ll
    !0 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !1, producer: "rustc version 1.11.0 (9b21dcd6a 2016-08-15)", isOptimized: false, runtimeVersion: 0, emissionKind: 1, enums: !2, subprograms: !3)
    !1 = !DIFile(filename: "./line.rs", directory: "/tmp")
    !2 = !{}
    !3 = !{!4}
    !4 = distinct !DISubprogram(name: "foo", linkageName: "_ZN4line3fooE", scope: !6, file: !5, line: 2, type: !7, isLocal: false, isDefinition: true, scopeLine: 2, flags: DIFlagPrototyped, isOptimized: false, templateParams: !2, variables: !2)
    !5 = !DIFile(filename: "/tmp/line.rs", directory: "/tmp")
    !6 = !DINamespace(name: "line", scope: null)
    !7 = !DISubroutineType(types: !8)
    !8 = !{!9, !9, !9}
    !9 = !DIBasicType(name: "i32", size: 32, align: 32, encoding: DW_ATE_signed)
    !10 = !{i32 2, !"Debug Info Version", i32 3}
    !11 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !5, line: 3, type: !9)
    !12 = !DIExpression()
    !13 = !DILocation(line: 3, scope: !4)
    !14 = !DILocalVariable(name: "y", arg: 2, scope: !4, file: !5, line: 4, type: !9)
    !15 = !DILocation(line: 4, scope: !4)
    !16 = !DILocation(line: 6, scope: !17)
    !17 = distinct !DILexicalBlock(scope: !4, file: !5, line: 6)
    !18 = !DILocation(line: 6, scope: !4)
    

    In 1.12.0, it added the extra locals, and the formal parameters took on line 2 like the subprogram.

    $ rustc +1.12.0 -g line.rs --emit=llvm-ir && sed -n '/DIComp/,$p' line.ll
    !0 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !1, producer: "rustc version 1.12.0 (3191fbae9 2016-09-23)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
    !1 = !DIFile(filename: "./line.rs", directory: "/tmp")
    !2 = !{}
    !3 = !{i32 2, !"Debug Info Version", i32 3}
    !4 = distinct !DISubprogram(name: "foo", linkageName: "_ZN4line3fooE", scope: !6, file: !5, line: 2, type: !8, isLocal: false, isDefinition: true, scopeLine: 2, flags: DIFlagPrototyped, isOptimized: false, unit: !0, templateParams: !2, variables: !2)
    !5 = !DIFile(filename: "/tmp/line.rs", directory: "/tmp")
    !6 = !DINamespace(name: "line", scope: null, file: !7)
    !7 = !DIFile(filename: "<unknown>", directory: "")
    !8 = !DISubroutineType(types: !9)
    !9 = !{!10, !10, !10}
    !10 = !DIBasicType(name: "i32", size: 32, align: 32, encoding: DW_ATE_signed)
    !11 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !5, line: 2, type: !10)
    !12 = !DIExpression()
    !13 = !DILocation(line: 2, scope: !4)
    !14 = !DILocalVariable(name: "y", arg: 2, scope: !4, file: !5, line: 2, type: !10)
    !15 = !DILocalVariable(name: "x", scope: !16, file: !5, line: 3, type: !10)
    !16 = distinct !DILexicalBlock(scope: !4, file: !5, line: 6)
    !17 = !DILocation(line: 3, scope: !16)
    !18 = !DILocalVariable(name: "y", scope: !16, file: !5, line: 4, type: !10)
    !19 = !DILocation(line: 4, scope: !16)
    !20 = !DILocation(line: 3, scope: !4)
    !21 = !DILocation(line: 4, scope: !4)
    !22 = !DILocation(line: 6, scope: !16)
    !23 = !DILocation(line: 2, scope: !16)
    

    Then 1.13.0 shows the line 1 behavior like the current nightly.

    $ rustc +1.13.0 -g line.rs --emit=llvm-ir && sed -n '/DIComp/,$p' line.ll
    !0 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !1, producer: "rustc version 1.13.0 (2c6933acc 2016-11-07)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
    !1 = !DIFile(filename: "./line.rs", directory: "/tmp")
    !2 = !{}
    !3 = !{i32 2, !"Debug Info Version", i32 3}
    !4 = distinct !DISubprogram(name: "foo", linkageName: "_ZN4line3fooE", scope: !6, file: !5, line: 2, type: !8, isLocal: false, isDefinition: true, scopeLine: 2, flags: DIFlagPrototyped, isOptimized: false, unit: !0, templateParams: !2, variables: !2)
    !5 = !DIFile(filename: "/tmp/line.rs", directory: "/tmp")
    !6 = !DINamespace(name: "line", scope: null, file: !7)
    !7 = !DIFile(filename: "<unknown>", directory: "")
    !8 = !DISubroutineType(types: !9)
    !9 = !{!10, !10, !10}
    !10 = !DIBasicType(name: "i32", size: 32, align: 32, encoding: DW_ATE_signed)
    !11 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !5, line: 1, type: !10)
    !12 = !DIExpression()
    !13 = !DILocation(line: 1, scope: !4)
    !14 = !DILocalVariable(name: "y", arg: 2, scope: !4, file: !5, line: 1, type: !10)
    !15 = !DILocalVariable(name: "x", scope: !16, file: !5, line: 3, type: !10)
    !16 = distinct !DILexicalBlock(scope: !4, file: !5, line: 6)
    !17 = !DILocation(line: 3, scope: !16)
    !18 = !DILocalVariable(name: "y", scope: !16, file: !5, line: 4, type: !10)
    !19 = !DILocation(line: 4, scope: !16)
    !20 = !DILocation(line: 3, scope: !4)
    !21 = !DILocation(line: 4, scope: !4)
    !22 = !DILocation(line: 6, scope: !16)
    !23 = !DILocation(line: 2, scope: !16)
    

    Josh Stone at 2017-10-03 21:30:37

  2. With the latest nightly, it no longer has the extra local declarations (perhaps thanks to #44573), but the formal-parameter line numbers are still just 1.

    $ rustc +nightly -Vv
    rustc 1.23.0-nightly (ee2286149 2017-11-07)
    binary: rustc
    commit-hash: ee2286149a5f0b148334841d4f067dc819dcca3b
    commit-date: 2017-11-07
    host: x86_64-unknown-linux-gnu
    release: 1.23.0-nightly
    LLVM version: 4.0
    
    $ rustc +nightly -g line.rs --emit=llvm-ir
    $ sed -n '/DIComp/,$p' line.ll
    !0 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !1, producer: "clang LLVM (rustc version 1.23.0-nightly (ee2286149 2017-11-07))", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
    !1 = !DIFile(filename: "line.rs", directory: "/tmp")
    !2 = !{}
    !3 = !{i32 2, !"Debug Info Version", i32 3}
    !4 = distinct !DISubprogram(name: "foo", linkageName: "_ZN4line3fooE", scope: !5, file: !1, line: 2, type: !7, isLocal: false, isDefinition: true, scopeLine: 2, flags: DIFlagPrototyped, isOptimized: false, unit: !0, templateParams: !2, variables: !2)
    !5 = !DINamespace(name: "line", scope: null, file: !6)
    !6 = !DIFile(filename: "<unknown>", directory: "")
    !7 = !DISubroutineType(types: !8)
    !8 = !{!9, !9, !9}
    !9 = !DIBasicType(name: "i32", size: 32, encoding: DW_ATE_signed)
    !10 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !1, line: 1, type: !9)
    !11 = !DIExpression()
    !12 = !DILocation(line: 1, scope: !4)
    !13 = !DILocalVariable(name: "y", arg: 2, scope: !4, file: !1, line: 1, type: !9)
    !14 = !DILocation(line: 6, scope: !4)
    
    $ rustc +nightly -g line.rs
    $ dwgrep 'entry ?(@AT_name == "foo") child*' libline.so
    [2f]    subprogram
            low_pc  0x42570
            high_pc 54
            frame_base      0..0xffffffffffffffff:0 reg6
            linkage_name    "_ZN4line3fooE"
            name    "foo"
            decl_file       "/tmp/line.rs"
            decl_line       2
            type    [6a] base_type
            external        true
    [4c]    formal_parameter
            location        0..0xffffffffffffffff:0 fbreg <-8>
            name    "x"
            decl_file       "/tmp/line.rs"
            decl_line       1
            type    [6a] base_type
    [5a]    formal_parameter
            location        0..0xffffffffffffffff:0 fbreg <-4>
            name    "y"
            decl_file       "/tmp/line.rs"
            decl_line       1
            type    [6a] base_type
    

    Josh Stone at 2017-11-08 01:16:19

  3. This was fixed in Rust 1.40 (godbolt). wg-debugging would like to see a codegen test added to prevent regressions and then this can be closed.

    Wesley Wiser at 2022-07-18 14:24:12