Description
Background
Original issue
The call to panic
within a function like Option::unwrap
is translated to LLVM as a tail call
(as it will never return), when multiple calls to the same function like this are inlined LLVM will notice the common tail call
block (i.e., loading the same panic string + location info and then calling panic
) and merge them together.
When merging these instructions together, LLVM will also attempt to merge the debug locations as well, but this fails (i.e., debug info is dropped) as Rust emits a new DISubprogram
at each inline site thus LLVM doesn't recognize that these are actually the same function and so thinks that there isn't a common debug location.
As an example of this, consider the following program:
#[no_mangle]
fn add_numbers(x: &Option<i32>, y: &Option<i32>) -> i32 {
let x1 = x.unwrap();
let y1 = y.unwrap();
x1 + y1
}
When building for x86_64 Windows using 1.72 it generates (note the lack of .cv_loc
before the call to panic
, thus it will be attributed to the same line at the addq
instruction):
.cv_loc 0 1 3 0 # src\lib.rs:3:0
addq $40, %rsp
retq
leaq .Lalloc_f570dea0a53168780ce9a91e67646421(%rip), %rcx
leaq .Lalloc_629ace53b7e5b76aaa810d549cc84ea3(%rip), %r8
movl $43, %edx
callq _ZN4core9panicking5panic17h12e60b9063f6dee8E
int3
Ideally, we would generate debug information that would allow LLVM to (partially) merge the locations together and thus maintain the correct inlining information and partial line info (since the inlined function was de-duplicated, we can't give the exact line number as that is ambiguous between the two inline sites, but we also shouldn't show an incorrect line number).
For example, we could generate something like below (which shows the panic!
was inlined from unwrap
in option.rs
at line 935 into the current function in lib.rs
at line 0):
.cv_loc 0 1 3 0 # src\lib.rs:3:0
addq $40, %rsp
retq
.cv_inline_site_id 6 within 0 inlined_at 1 0 0
.cv_loc 6 2 935 0 # library\core\src\option.rs:935:0
leaq .Lalloc_5f55955de67e57c79064b537689facea(%rip), %rcx
leaq .Lalloc_e741d4de8cb5801e1fd7a6c6795c1559(%rip), %r8
movl $43, %edx
callq _ZN4core9panicking5panic17hde1558f32d5b1c04E
int3
New issue
I attempted to fix this by deduplicating sub-program, lexical block and variable debug info in #114643 however this resulted in asserts in LLVM when attempting to build for Linux (see #115156).
The root cause of those asserts is that Rust was generating overlapping DW_OP_LLVM_fragment
info for the same local variable in two different @llvm.dbg.declare
calls (i.e., that two different stack allocations represented the same part ("fragment") of the same local variable).
My current plan (#115417) is to work around this by not deduplicating lexical scopes and variables, but instead create a new lexical scope every time that a function is inlined so that subsequent lexical scopes and variables do not appear to be duplicates (from LLVM's perspective).
Long-term fix
Given that LLVM relies on exact matching to merge together debug locations, and that Clang appears to do this deduplicating in its debug information, it seems that the correct long-term fix is for Rust to also do this deduplicating.
However, to do this Rust needs to keep careful track of how locals and scopes are duplicated while inlining - either to avoid the duplication at the point of inlining or to be able to deduplicate them when emitting debug information.