Description
I tried this code:
struct Foo {
a: usize,
}
impl Foo {
#[inline(never)]
fn get_a(&self) -> Option<usize> {
Some(self.a)
}
#[inline(never)]
fn var_return(&self) -> usize {
let r = self.get_a().unwrap();
r
}
#[inline(never)]
fn var_return_opt_unwrap(&self) -> Option<usize> {
let r = self.get_a().unwrap();
Some(r)
}
#[inline(never)]
fn var_return_opt_match(&self) -> Option<usize> {
let r = match self.get_a() {
None => return None,
Some(a) => a,
};
Some(r)
}
#[inline(never)]
fn var_return_opt_try(&self) -> Option<usize> {
let r = self.get_a()?;
Some(r)
}
}
fn main() {
let f1 = Foo{ a: 1 };
let f2 = Foo{ a: 1 };
f1.var_return();
f1.var_return_opt_unwrap();
f1.var_return_opt_match();
f2.var_return_opt_try();
}
Setting a breakpoint on var_return_opt_try
in rust-gdb results in gdb stopping before self
's stack slot is initialized, and gdb dereferences it to print wrong values based on self
:
~/code/scrap/rust/debuginfo: cargo clean; RUSTFLAGS=-Zmir-enable-passes=-SingleUseConsts cargo +nightly build
Removed 0 files
Compiling debuginfo v0.1.0 (/home/vich/code/scrap/rust/debuginfo)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
~/code/scrap/rust/debuginfo: rust-gdb target/debug/debuginfo -batch -ex "b debuginfo::Foo::var_return_opt_try" -ex "r" -ex "disas" -ex "p self" -ex "n" -ex "p self"
Breakpoint 1 at 0x33d68: file std/src/panicking.rs, line 862.
Breakpoint 2 at 0x13c24: file src/main.rs, line 34.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Breakpoint 2, debuginfo::Foo::var_return_opt_try (self=0x7fffffffe0f8) at src/main.rs:34
34 let r = self.get_a()?;
Dump of assembler code for function _ZN9debuginfo3Foo18var_return_opt_try17hed204febe8a0db96E:
0x0000555555567c20 <+0>: sub $0x38,%rsp
=> 0x0000555555567c24 <+4>: mov %rdi,0x28(%rsp)
0x0000555555567c29 <+9>: call 0x555555567ae0 <debuginfo::Foo::get_a>
0x0000555555567c2e <+14>: mov %rax,%rdi
0x0000555555567c31 <+17>: mov %rdx,%rsi
0x0000555555567c34 <+20>: call 0x555555567d20 <core::option::{impl#39}::branch<usize>>
0x0000555555567c39 <+25>: mov %rax,0x18(%rsp)
0x0000555555567c3e <+30>: mov %rdx,0x20(%rsp)
0x0000555555567c43 <+35>: cmpq $0x0,0x18(%rsp)
0x0000555555567c49 <+41>: jne 0x555555567c65 <debuginfo::Foo::var_return_opt_try+69>
0x0000555555567c4b <+43>: mov 0x20(%rsp),%rax
0x0000555555567c50 <+48>: mov %rax,0x30(%rsp)
0x0000555555567c55 <+53>: mov %rax,0x10(%rsp)
0x0000555555567c5a <+58>: movq $0x1,0x8(%rsp)
0x0000555555567c63 <+67>: jmp 0x555555567c74 <debuginfo::Foo::var_return_opt_try+84>
0x0000555555567c65 <+69>: call 0x555555567d00 <core::option::{impl#40}::from_residual<usize>>
0x0000555555567c6a <+74>: mov %rax,0x8(%rsp)
0x0000555555567c6f <+79>: mov %rdx,0x10(%rsp)
0x0000555555567c74 <+84>: mov 0x8(%rsp),%rax
0x0000555555567c79 <+89>: mov 0x10(%rsp),%rdx
0x0000555555567c7e <+94>: add $0x38,%rsp
0x0000555555567c82 <+98>: ret
End of assembler dump.
$1 = (*mut debuginfo::Foo) 0x7fffffffe0f8
35 Some(r)
$2 = (*mut debuginfo::Foo) 0x7fffffffe100
~/code/scrap/rust/debuginfo: rust-gdb --version
GNU gdb (GDB) 15.1
Note the incorrect different values of self
as I step past initialization. The breakpoint should trigger just after that mov
, not before it. The other functions in that program indeed do it correctly. Oddly, it seems like the ?
operator is necessary for my repro, though maybe that has something to do with the hidden residual
var.
(Note that f1
vs f2
is necessary to actually avoid accidental correct values of self
due to stack reuse, so the other functions can't print wrong self
as written. But I'm just looking at where the breakpoint hits in disassembly.)
Here's relevant llvm-dwarfdump from this binary, with a DW_TAG_lexical_block
pointing at at the mov
(0x...24
) rather than the call
:
0x0000049a: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000013c20)
DW_AT_high_pc (0x0000000000013c83)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_specification (0x0000017b "_ZN9debuginfo3Foo18var_return_opt_try17hed204febe8a0db96E")
0x000004ad: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg +40)
DW_AT_name ("self")
DW_AT_decl_file ("/home/vich/code/scrap/rust/debuginfo/src/main.rs")
DW_AT_decl_line (33)
DW_AT_type (0x00000324 "debuginfo::Foo *")
0x000004bb: DW_TAG_lexical_block
DW_AT_ranges (0x00000060
[0x0000000000013c24, 0x0000000000013c29)
[0x0000000000013c65, 0x0000000000013c74))
0x000004c0: DW_TAG_variable
DW_AT_location (DW_OP_fbreg +7)
DW_AT_name ("residual")
DW_AT_decl_file ("/home/vich/code/scrap/rust/debuginfo/src/main.rs")
DW_AT_decl_line (34)
DW_AT_type (0x0000027d "core::option::Option<core::convert::Infallible>")
0x000004ce: NULL
0x000004cf: DW_TAG_lexical_block
DW_AT_low_pc (0x0000000000013c55)
DW_AT_high_pc (0x0000000000013c63)
0x000004dc: DW_TAG_variable
DW_AT_location (DW_OP_fbreg +48)
DW_AT_name ("r")
DW_AT_alignment (1)
DW_AT_decl_file ("/home/vich/code/scrap/rust/debuginfo/src/main.rs")
DW_AT_decl_line (34)
DW_AT_type (0x000001e6 "usize")
0x000004eb: NULL
0x000004ec: NULL
0x000004ed: NULL
This looks pretty related to #128945 and #113819.
Like 113819, the repro hits on 1.71 but not 1.70. I'm filing separately because, unlike it, disabling SingleUseConsts doesn't seem to fix the breakpoint. I'm not 100% sure I'm disabling it right - you can see my cargo build
command above.
Meta
rustc --version --verbose
:
rustc 1.83.0-nightly (4ac7bcbaa 2024-09-04)
binary: rustc
commit-hash: 4ac7bcbaad8d6fd7a51bdf1b696cbc3ba4c796cf
commit-date: 2024-09-04
host: x86_64-unknown-linux-gnu
release: 1.83.0-nightly
LLVM version: 19.1.0
Backtrace
<backtrace>