Repository: rust
Issue: Inaccurate DWARF call stacks with optimizations
Labels: A-debuginfo,T-compiler,C-bug,S-needs-repro,E-needs-investigation

NB: I'm not sure if this is actually a bug, or just a limitation of DWARF-based call graph profiling.

---

I'm experiencing an issue where, when profiling an optimized Rust binary using `perf` with DWARF call graphs, functions appear in incorrect places in the call graph.

**Steps to reproduce:**

1. Build with `rustc 1.86.0-nightly (f85c6de55 2025-01-26)`:
   ```rust
   use std::collections::HashMap;

   #[inline(never)]
   #[no_mangle]
   fn merge(lhs: &mut HashMap<i32, f32>, rhs: &HashMap<i32, f32>) {
       for (k, v) in rhs {
           lhs.insert(*k, *v);
       }
   }

   #[no_mangle]
   #[inline(never)]
   fn merge_all(inputs: Vec<(HashMap<i32, f32>, HashMap<i32, f32>)>) {
       for (mut lhs, rhs) in inputs {
           merge(&mut lhs, &rhs);
           std::mem::forget(core::hint::black_box((lhs, rhs)));
       }
   }

   pub fn main() {
       for _ in 0..1000 {
           let mut a = HashMap::<i32, f32>::default();

           for key in 0..100 {
               a.insert(key, key as f32);
           }

           let mut b = HashMap::default();
           b.insert(-1, -1.0);

           let inputs = std::iter::from_fn(|| Some((a.clone(), b.clone())))
               .take(1000)
               .collect::<Vec<_>>();

           merge_all(inputs);
       }
   }
   ```

   ```toml
   [package]
   name = "test-stacks"
   version = "0.1.0"
   edition = "2021"
   
   [profile.release]
   debug = "full"
   lto = false
   ```

   Compile with:

   ```
   RUSTFLAGS="-C force-frame-pointers=yes -Z merge-functions=disabled -C link-arg=-Wl,--icf=none" \
     cargo +nightly -Z build-std build --release
   ```

2. Profile and open the report:

   ```
   sudo perf record -g --call-graph dwarf,16384 -F 20000 -k mono -- target/release/test-stacks
   sudo perf report -g --percent-limit 0
   ```

**Profiler output:**

```
-   92.91%     0.00%  test-stacks  test-stacks                [.] main                                                                                                          ◆
     main                                                                                                                                                                       ▒
     std::rt::lang_start_internal                                                                                                                                               ▒
     std::panic::catch_unwind (inlined)                                                                                                                                         ▒
     std::panicking::try (inlined)                                                                                                                                              ▒
     std::panicking::try::do_call (inlined)                                                                                                                                     ▒
   - std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h645042a4ac672685 (inlined)                                                                                    ▒
      - 92.91% std::panic::catch_unwind (inlined)                                                                                                                               ▒
           std::panicking::try (inlined)                                                                                                                                        ▒
           std::panicking::try::do_call (inlined)                                                                                                                               ▒
           core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once (inlined)                                                                        ▒
           std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h5cf5b7758a7d9323                                                                                                 ▒
           std::sys::backtrace::__rust_begin_short_backtrace                                                                                                                    ▒
         - core::ops::function::FnOnce::call_once (inlined)                                                                                                                     ▒
            - 86.83% test_stacks::main                                                                                                                                          ▒
               - test_stacks::bench_map_large_merge (inlined)                                                                                                                   ▒
                  + 62.44% core::iter::traits::iterator::Iterator::collect (inlined)                                                                                            ▒
do_bench --->     - 22.18% do_bench                                                                                                                                             ▒
                     + 14.85% cfree@GLIBC_2.2.5                                                                                                                                 ▒
                     + 2.96% core::ptr::drop_in_place<std::collections::hash::map::HashMap<i32,f32>> (inlined)                                                                  ▒
                     - 1.90% merge                                                                                                                                              ▒
hashmap insert --->     - 1.58% std::collections::hash::map::HashMap<K,V,S>::insert (inlined)                                                                                   ▒
                             0.52% _int_free                                                                                                                                    ▒
                             0.36% _int_free_merge_chunk                                                                                                                        ▒
                             0.28% cfree@GLIBC_2.2.5                                                                                                                            ▒
                             0.15% _int_free_create_chunk                                                                                                                       ▒
do_bench? --------->       - 0.12% do_bench                                                                                                                                     ▒
                              + 0.11% core::ptr::drop_in_place<std::collections::hash::map::HashMap<i32,f32>> (inlined)                                                         ▒
                              - 0.00% <alloc::vec::into_iter::IntoIter<T,A> as core::iter::traits::iterator::Iterator>::next (inlined)                                          ▒
                                   0.00% <core::ptr::non_null::NonNull<T> as core::cmp::PartialEq>::eq (inlined)                                                                ▒
                                 - 0.00% core::ptr::non_null::NonNull<T>::read (inlined)                                                                                        ▒
                                      core::ptr::read (inlined)                                                                                                                 ▒
                                0.00% core::hint::black_box (inlined)                                                                                                           ▒
                              - 0.00% <alloc::vec::Vec<T,A> as core::iter::traits::collect::IntoIterator>::into_iter (inlined)                                                  ▒
                                   core::mem::manually_drop::ManuallyDrop<T>::new (inlined)
```


- In the profiler output, `do_bench` appears as a child of `HashMap::insert`, which doesn't correspond to the actual call hierarchy in the source code.
- I've tried enabling frame pointers, disabling function merging, LTO, and linker-based ICF, which does not resolve the issue.

**Expected behavior:**

I expected the call stacks in the profiler to accurately reflect the function call hierarchy, with `do_bench` not appearing as a child of `HashMap::insert`.