Skip to content

DWARF line table rows with line 0 are dropped, so code gets attributed to the wrong line #9164

Description

@deadprogram

While working on some debugger related fixes in TinyGo, I discovered what I think is an issue, and also a possible fix. The following is an edited version of the automated analysis.

Thank you for looking it over.

Analysis

When wasm-opt rewrites the DWARF line table, it drops every row that has line 0, even when no passes run. In DWARF, line 0 means the code has no source line, for example code LLVM merged from two places. Once the line 0 row is gone, the previous row covers that code, so debuggers and profilers attribute it to the wrong line.

The check is in LineState::needToEmit (wasm-debug.cpp):

bool needToEmit() {
  // Zero values imply we can ignore this line.
  // https://github.com/WebAssembly/debugging/issues/9#issuecomment-567720872
  return line != 0 && addr != 0;
}

The linked comment explains why address 0 is reserved for dead code removed by the linker. It does not mention line 0. The code is the same on main.

Repro

t.c:

int g(int);

int f(int x) {
  int r = 0;
  if (x > 10)
    r = g(x);
  else
    r = g(x + 1);
  return r * 3;
}
clang --target=wasm32 -O2 -g -c t.c -o t.o
wasm-ld --no-entry --export=f --allow-undefined t.o -o t.wasm
wasm-opt -g t.wasm -o out.wasm
llvm-dwarfdump --debug-line t.wasm
llvm-dwarfdump --debug-line out.wasm

LLVM merges the two calls to g and marks the call with line 0. Input line table:

Address            Line   Column File   ISA Discriminator OpIndex Flags
0x0000000000000002      3      0      1   0             0       0  is_stmt
0x0000000000000007      5      9      1   0             0       0  is_stmt prologue_end
0x0000000000000008      5      7      1   0             0       0
0x000000000000000b      0      0      1   0             0       0
0x0000000000000013      9     12      1   0             0       0  is_stmt
0x0000000000000014      9      3      1   0             0       0
0x0000000000000015      9      3      1   0             0       0  end_sequence

Output line table after wasm-opt -g with no passes:

Address            Line   Column File   ISA Discriminator OpIndex Flags
0x0000000000000002      3      0      1   0             0       0  is_stmt
0x0000000000000007      5      9      1   0             0       0  is_stmt prologue_end
0x0000000000000008      5      7      1   0             0       0
0x000000000000000f      9     12      1   0             0       0  is_stmt
0x0000000000000010      9      3      1   0             0       0
0x0000000000000011      9      3      1   0             0       0  end_sequence

The call is at 0x0b in both files. llvm-dwarfdump --lookup=0x0b gives line 0 for t.wasm and line 5, column 7 (the if condition) for out.wasm.

Tested with wasm-opt version 133 and clang 18.1.2 from wasi-sdk.

Impact

We found this in TinyGo, where a no-op round trip through wasm-opt -g removes all 21,756 line 0 rows from a small program. It gets worse with --asyncify, since code it adds in the middle of a function also ends up under the previous source line. See tinygo-org/tinygo#5736.

Possible fix

Keep line 0 rows and only skip rows with address 0. Line 0 rows would still need their address mapped like any other row.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions