diff --git a/crates/cranelift/src/debug/transform/unit.rs b/crates/cranelift/src/debug/transform/unit.rs index e4164942cbee..64f05a5578b4 100644 --- a/crates/cranelift/src/debug/transform/unit.rs +++ b/crates/cranelift/src/debug/transform/unit.rs @@ -244,6 +244,15 @@ where Ok(wrapper_die_id) } +fn is_dead_code(entry: &DebuggingInformationEntry) -> bool { + const TOMBSTONE: u64 = u32::MAX as u64; + + match entry.attr_value(gimli::DW_AT_low_pc) { + Ok(Some(AttributeValue::Addr(addr))) => addr == TOMBSTONE, + _ => false, + } +} + pub(crate) fn clone_unit<'a, R>( dwarf: &gimli::Dwarf, unit: Unit, @@ -346,12 +355,20 @@ where let mut current_value_range = InheritedAttr::new(); let mut current_scope_ranges = InheritedAttr::new(); while let Some((depth_delta, entry)) = entries.next_dfs()? { + // If `skip_at_depth` is `Some` then we previously decided to skip over + // a node and all it's children. Let A be the last node processed, B be + // the first node skipped, C be previous node, and D the current node. + // Then `depth` is the difference from A to C, `cached` is the diffence + // from A to B, and `depth_delta` is the differenc from C to D. let depth_delta = if let Some((depth, cached)) = skip_at_depth { + // `new_depth` = B to D let new_depth = depth + depth_delta; + // if D is below B continue to skip if new_depth > 0 { skip_at_depth = Some((new_depth, cached)); continue; } + // otherwise process D with `depth_delta` being the difference from A to D skip_at_depth = None; new_depth + cached } else { @@ -361,6 +378,7 @@ where if !context .reachable .contains(&entry.offset().to_unit_section_offset(&unit)) + || is_dead_code(&entry) { // entry is not reachable: discarding all its info. skip_at_depth = Some((0, depth_delta)); diff --git a/tests/all/debug/testsuite/dead_code.c b/tests/all/debug/testsuite/dead_code.c new file mode 100644 index 000000000000..61067a78daa8 --- /dev/null +++ b/tests/all/debug/testsuite/dead_code.c @@ -0,0 +1,19 @@ +int bar(int a) +{ + int b[50]; + b[0] = a; + b[29] = a; + return a; +} + +int baz(int a); + +__attribute__((export_name("foo"))) int foo() +{ + return baz(10); +} + +__attribute__((noinline)) int baz(int a) +{ + return a + 5; +} diff --git a/tests/all/debug/testsuite/dead_code.wasm b/tests/all/debug/testsuite/dead_code.wasm new file mode 100755 index 000000000000..b8c28a1f63d4 Binary files /dev/null and b/tests/all/debug/testsuite/dead_code.wasm differ diff --git a/tests/all/debug/translate.rs b/tests/all/debug/translate.rs index db5e2af9355d..4e72dcd39f0c 100644 --- a/tests/all/debug/translate.rs +++ b/tests/all/debug/translate.rs @@ -43,6 +43,30 @@ fn check_line_program(wasm_path: &str, directives: &str) -> Result<()> { Ok(()) } +#[test] +#[ignore] +#[cfg(all( + any(target_os = "linux", target_os = "macos"), + target_pointer_width = "64" +))] +fn test_debug_dwarf_translate_dead_code() -> Result<()> { + check_wasm( + "tests/all/debug/testsuite/dead_code.wasm", + r##" +check: DW_TAG_compile_unit +# We don't have "bar" function because it is dead code +not: DW_AT_name ("bar") +# We have "foo" function +check: DW_TAG_subprogram +check: DW_AT_name ("foo") +# We have "baz" function +# it was marked `noinline` so isn't dead code +check: DW_TAG_subprogram +check: DW_AT_name ("baz") + "##, + ) +} + #[test] #[ignore] #[cfg(all(