Conversation
|
This looks large and complicated, and we don't have deep DWARF expertise here, so I am worried. But let me ask first, as background: what is a |
|
Thanks. “nonzero tombstone” was imprecise shorthand rather than a formal DWARF term. The all-ones address is documented by DWARF issue 200609.1, accepted for DWARF v6, as the reserved address for a non-existent entity: https://dwarfstd.org/issues/200609.1.html LLVM implements this as https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/BinaryFormat/Dwarf.h The max-minus-one value ( https://reviews.llvm.org/D81784 So Binaryen already recognizes I will update the PR wording to use the precise terms and references. I can also split the small tombstone-preservation change from the broader scope-range repair to make the review easier. |
|
Thanks for the info. After reading some of that, I am afraid I don't think I have the expertise to review this. Can you say more about the use case that you want this for? Perhaps there is another way to achieve it. For example, our source maps support is a lot more robust, and maybe that is enough - it does provide source locations through transformations? |
Preserve nonzero tombstones, reject lost or reversed low/high pairs, normalize range lists, and repair parent scope ranges from surviving children. Ambiguous sibling scopes are made unavailable instead of being assigned incorrect code ranges.
eb002d1 to
a16642f
Compare
|
Thanks for following up! Let me clarify the fundamental difference in use case between Source Maps and DWARF, share concrete E2E verification results, provide options for splitting this PR to ease review, and provide reproducible test artifacts demonstrating why this change is essential. 1. Capability Comparison: Source Maps vs. DWARFSource maps and DWARF address two entirely different layers of debugging in WebAssembly:
2. Two Concrete Facts & VerificationFact 1: Source Maps fundamentally cannot inspect or evaluate native variablesWe verified this directly in Chrome DevTools / V8 Inspector Protocol. When paused at a breakpoint inside
Source maps only specify bytecode-to-source-line mappings. They have no protocol representation for variable names, stack offsets, or type layouts. DWARF is strictly required for actual variable inspection in native debuggers (Chrome DevTools DWARF extension, LLDB, GDB). Fact 2: Current
|
|
Thanks for the info, but I'd still like to understand your use case better. Specifically, you say you are doing this:
Can you debug a build without Asyncify? E.g. using JSPI instead, which is much more efficient. JSPI requires a modern browser, but if you are debugging locally, that is not a problem. In general that is what people do: use source maps for stack traces, and for full local debugging, use DWARF without Asyncify or wasm-opt. |
|
Yes, a JSPI build could be useful for debugging an isolated C/JavaScript interaction. For LLGo, though, Asyncify is part of the supported browser execution path: Emscripten Fibers use it to suspend Go goroutines while preserving synchronous C/C++ calls. JSPI handles the Promise boundary, but switching this runtime to JSPI also requires a different Go continuation and scheduler implementation. A debug-only JSPI build would therefore not reproduce bugs in the shipped execution path. We also tested a normal LLGo browser build with Emscripten 6.0.8 and Binaryen 132, rather than only the Binaryen fixture. Its final Wasm has 81 compilation units. Stock Binaryen produces 125 DWARF parent-containment errors and 24 overlaps; the LLGo backport passes |
|
I see, thanks for the extra context. Ok, those do sound like good reasons to move forward with this. I'll look into reviewing this PR. |
|
Independent follow-up review found one additional regression beyond the inline comments: for a wasm64 CU, the vendored DWARFYAML emitter still writes 4-byte .debug_ranges entries, while the new repair had appended a list using 8-byte-CU offsets. A two-function wasm64 fixture verified with llvm-dwarfdump reproduced invalid DW_AT_ranges after this PR but not on main. Commit 34ba0d6 keeps non-wasm32 CUs on the previous updater path and adds a regression test. Full 64-bit range-list emission remains a separate limitation of the vendored emitter. Post-review local validation: 430 C++ tests passed (1 existing platform skip), all 6 DWARF Python tests passed, the complete non-torture wasm-opt suite passed, and the wasm64 probe now verifies with the same address-size warnings as main. |
|
Chiming in from outside, I just found this PR and verified locally that it fixes an issue I was struggling with. In brief, I've got wasm builds of an extensive rust library. I need to symbolicate a stripped trace log generated by clients. I could have done this using source maps. However, optimization would have limited the information we could restore with that method. Being able to use DWARF allows us to reconstruct in more detail as well as keeping the same tooling we use for other build/distribution formats. It seems like you have this in hand, and I'm not an expert in this area, but if there's any way I can help on this PR let me know. |
|
@evanandel Thanks for testing this on your Rust workload and sharing the use case. Independent confirmation that this helps symbolicate optimized client traces is valuable context for the review. If you have a shareable |
|
Out of curiosity I pointed Gemini at the tests here and asked it to write code that gets the tests to pass. It came up with this: https://github.com/WebAssembly/binaryen/compare/main...kripken:binaryen:dwarf.tombstones?expand=1 I didn't read this carefully, but it appears shorter and simpler than this PR. Are there important fixes in this PR which are not tested? |
|
Thanks for prototyping the smaller fix. I built I added two focused regression tests in a550175d. Both pass here and fail with the smaller patch:
So the smaller patch covers much of the tombstone and invalid low/high-PC work, but it does not cover these range-list and scope-tree cases. The tombstone portion looks separable if splitting would help review. These two tests do not cover every branch of the topology repair; I can add focused parent-containment and range-list-union tests as we narrow the review. |



Summary
Binaryen currently updates DWARF range endpoints independently. When optimization or Asyncify removes or reorders expressions, the resulting endpoints can wrap, overlap, or escape their parent scope. This is the same failure mode reported in #6406, extended to range lists and scope topology.
The repair is conservative: representable parent unions are preserved, while ambiguous scopes fail closed. Empty replacement range lists are appended rather than mutating lists that another DIE may share.
Implementation
Range-set normalization, union, containment, and overlap are isolated in
DwarfRangesand directly unit-tested. The DWARF adapter keeps encoding-specific constants and tombstone rules inwasm-debug.cpp, including the distinction between a validlow_pc = 0and range-list terminators.The repair builds an explicit parent/child index for each compilation unit. It first propagates malformed or unavailable scopes through that tree, then processes children before parents so sibling overlap checks see final ranges and range-list parents can be extended before their own containment check. This also avoids repeated descendant scans and avoids relying on default depths for null DIE terminators.
Tombstone handling
"Nonzero tombstone" was imprecise shorthand. DWARF issue 200609.1, accepted for DWARF v6, reserves the largest representable target address (for example,
0xfffffffffor wasm32) for a non-existent entity. LLVM implements this asdwarf::computeTombstoneAddressand has a WebAssembly-specific test for a dead wasm32 subprogram.The max-minus-one value (
-2) is an LLVM legacy compatibility encoding rather than a general DWARF value. It is recognized for legacy.debug_ranges/.debug_locdata because all-ones is already the base-address-selection marker and(0, 0)terminates the list. See LLVM D81784 and DWARFDebugRangeList.cpp.Binaryen already recognizes
0,-1, and-2in tombstone-aware contexts. This change preventsupdateDIEfrom passing-1/-2through the instruction-offset mapper, where they can be rewritten to zero and make a dead DIE appear to refer to address zero.Validation
DwarfRangestestspython3 check.py wasm-opt --no-torturesuiteDW_AT_low_pc = 0xffffffffvalues remain intactllvm-dwarfdump --verifyafter roundtrip forclass_with_dwarf_noprint,fannkuch3_manyopts_dwarf,fib2_dwarf,fib2_emptylocspan_dwarf,ignore_missing_func_dwarf,inlined_to_start_dwarf, andreverse_dwarf_abbrevsllvm-dwarfdump --verifyafter--asyncify -O -gforclass_with_dwarf_noprintA wasm32 object with
DW_AT_low_pc = 0xffffffffis reported by LLVM asdead code; before this fix,wasm-opt -O -grewrites it to0x00000000.Fixes #6406.