Skip to content

Repair invalid DWARF scope ranges after transforms - #8964

Open
cpunion wants to merge 3 commits into
WebAssembly:mainfrom
cpunion:codex/fix-dwarf-range-topology-6406
Open

Repair invalid DWARF scope ranges after transforms#8964
cpunion wants to merge 3 commits into
WebAssembly:mainfrom
cpunion:codex/fix-dwarf-range-topology-6406

Conversation

@cpunion

@cpunion cpunion commented Aug 2, 2026

Copy link
Copy Markdown

Summary

  • preserve the DWARF all-ones tombstone and LLVM's legacy max-minus-one encoding instead of remapping dead DIEs to address zero
  • reject lost or reversed low/high PC pairs without unsigned wraparound
  • normalize updated range lists and rebuild range-list parents from surviving child scopes
  • repair scope trees bottom-up and make ambiguous overlapping sibling subtrees unavailable

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 DwarfRanges and directly unit-tested. The DWARF adapter keeps encoding-specific constants and tombstone rules in wasm-debug.cpp, including the distinction between a valid low_pc = 0 and 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, 0xffffffff for wasm32) for a non-existent entity. LLVM implements this as dwarf::computeTombstoneAddress and 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_loc data 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 -2 in tombstone-aware contexts. This change prevents updateDIE from passing -1/-2 through the instruction-offset mapper, where they can be rewritten to zero and make a dead DIE appear to refer to address zero.

Validation

  • all 382 C++ unit tests, including direct DwarfRanges tests
  • full python3 check.py wasm-opt --no-torture suite
  • a minimal DWARF v4 roundtrip test asserting that two DW_AT_low_pc = 0xffffffff values remain intact
  • llvm-dwarfdump --verify after roundtrip for class_with_dwarf_noprint, fannkuch3_manyopts_dwarf, fib2_dwarf, fib2_emptylocspan_dwarf, ignore_missing_func_dwarf, inlined_to_start_dwarf, and reverse_dwarf_abbrevs
  • llvm-dwarfdump --verify after --asyncify -O -g for class_with_dwarf_noprint
  • a 73-CU Emscripten final module that previously reported containment, internal-overlap, and sibling-overlap errors now verifies with no errors after its final Asyncify transform

A wasm32 object with DW_AT_low_pc = 0xffffffff is reported by LLVM as dead code; before this fix, wasm-opt -O -g rewrites it to 0x00000000.

Fixes #6406.

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.
@kripken

kripken commented Aug 5, 2026

Copy link
Copy Markdown
Member

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 nonzero tombstone? Is that documented somewhere in LLVM or DWARF?

@cpunion

cpunion commented Aug 9, 2026

Copy link
Copy Markdown
Author

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 dwarf::computeTombstoneAddress. It also has a WebAssembly-specific test where DW_AT_low_pc = 0xffffffff marks a wasm32 subprogram as dead code:

https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/BinaryFormat/Dwarf.h
https://github.com/llvm/llvm-project/blob/main/llvm/test/tools/llvm-debuginfo-analyzer/WebAssembly/wasm-32bit-tombstone.s

The max-minus-one value (-2) is separate: it is an LLVM-recognized compatibility encoding for legacy .debug_ranges/.debug_loc, where all-ones is already the base-address-selection marker and (0, 0) terminates the list:

https://reviews.llvm.org/D81784
https://github.com/llvm/llvm-project/blob/main/llvm/lib/DebugInfo/DWARF/DWARFDebugRangeList.cpp

So -1 has DWARF backing, while -2 should be described as an LLVM legacy compatibility encoding, not a general DWARF value.

Binaryen already recognizes 0, -1, and -2 in isTombstone. The bug here is that updateDIE passes -1/-2 through the instruction-offset mapper, which rewrites them to zero and can make a dead DIE appear to refer to address zero. I reproduced that behavior with Binaryen 125: LLVM reports the input DW_AT_low_pc = 0xffffffff as dead code, but after wasm-opt -O -g it becomes 0x00000000.

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Impossible address ranges in DWARF debug info

2 participants