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.
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):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:LLVM merges the two calls to
gand marks the call with line 0. Input line table:Output line table after
wasm-opt -gwith no passes:The call is at 0x0b in both files.
llvm-dwarfdump --lookup=0x0bgives line 0 fort.wasmand line 5, column 7 (theifcondition) forout.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 -gremoves 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.