See the following code:
pub struct ListNode {
next: Option<Box<ListNode>>
}
pub fn foo(mut head: Box<ListNode>) -> Box<ListNode> {
let mut cur = &mut head;
while let Some(next) = std::mem::replace(&mut cur.next, None) {
cur.next = Some(next);
cur = cur.next.as_mut().unwrap();
}
head
}
Apparently the unwrap should never panic given the assignment in the previous line. However, based on the assembly output, that check isn't optimized away.
(The while let statement seems to be generating useless function calls for drop as well, btw.)
Minimal example
This no-op
pub fn foo(x: String) -> String {
Some(x).unwrap()
}
generates
core::ptr::real_drop_in_place:
movq %rdi, %rax
movq (%rdi), %rdi
testq %rdi, %rdi
je .LBB0_2
movq 8(%rax), %rsi
testq %rsi, %rsi
je .LBB0_2
movl $1, %edx
jmpq *__rust_dealloc@GOTPCREL(%rip)
.LBB0_2:
retq
playground::foo:
pushq %rbx
subq $32, %rsp
movups (%rsi), %xmm0
movaps %xmm0, (%rsp)
movq 16(%rsi), %rcx
movq %rcx, 16(%rsp)
cmpq $0, (%rsp)
je .LBB1_1
movq %rdi, %rax
movq 16(%rsp), %rcx
movq %rcx, 16(%rdi)
movaps (%rsp), %xmm0
movups %xmm0, (%rdi)
addq $32, %rsp
popq %rbx
retq
.LBB1_1:
leaq .Lanon.9b27569f3e5c884c6d6bffb1a8af3c8b.2(%rip), %rdi
callq *core::panicking::panic@GOTPCREL(%rip)
ud2
movq %rax, %rbx
movq %rsp, %rdi
callq core::ptr::real_drop_in_place
movq %rbx, %rdi
callq _Unwind_Resume@PLT
ud2
.Lanon.9b27569f3e5c884c6d6bffb1a8af3c8b.0:
.ascii "called `Option::unwrap()` on a `None` value"
.Lanon.9b27569f3e5c884c6d6bffb1a8af3c8b.1:
.ascii "src/libcore/option.rs"
.Lanon.9b27569f3e5c884c6d6bffb1a8af3c8b.2:
.quad .Lanon.9b27569f3e5c884c6d6bffb1a8af3c8b.0
.asciz "+\000\000\000\000\000\000"
.quad .Lanon.9b27569f3e5c884c6d6bffb1a8af3c8b.1
.asciz "\025\000\000\000\000\000\000\000[\001\000\000\025\000\000"
On playground for the latest nightly (2019-07-14 83e4eed16ef7adb54a80)
That reminds me that this issue may be from the enum size optimization. When we don't have a separate field for the enum tag (in case like Option<Box<_>>), compiler doesn't make use of non-null guarantee when optimizing it.
To prove that, an even simpler testcase is:
use std::num::NonZeroUsize;
pub fn foo(x: NonZeroUsize) -> NonZeroUsize {
Some(x).unwrap()
}
which produces the following assembly:
playground::foo:
pushq %rax
testq %rdi, %rdi
je .LBB0_1
movq %rdi, %rax
popq %rcx
retq
.LBB0_1:
leaq .Lanon.c21f24ad05c622049a6df4006ef31e03.2(%rip), %rdi
callq *core::panicking::panic@GOTPCREL(%rip)
ud2
.Lanon.c21f24ad05c622049a6df4006ef31e03.0:
.ascii "called `Option::unwrap()` on a `None` value"
.Lanon.c21f24ad05c622049a6df4006ef31e03.1:
.ascii "src/libcore/option.rs"
.Lanon.c21f24ad05c622049a6df4006ef31e03.2:
.quad .Lanon.c21f24ad05c622049a6df4006ef31e03.0
.asciz "+\000\000\000\000\000\000"
.quad .Lanon.c21f24ad05c622049a6df4006ef31e03.1
.asciz "\025\000\000\000\000\000\000\000[\001\000\000\025\000\000"
Hope that this will be improved soon considering the broad use of Option and the related functions unwrap, expect, ...
Most helpful comment
That reminds me that this issue may be from the enum size optimization. When we don't have a separate field for the enum tag (in case like
Option<Box<_>>), compiler doesn't make use of non-null guarantee when optimizing it.To prove that, an even simpler testcase is:
which produces the following assembly: