Rust: Rust fails to optimize away useless unwrap check

Created on 28 Dec 2018  路  3Comments  路  Source: rust-lang/rust

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.)

I-slow

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:

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"

All 3 comments

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, ...

Was this page helpful?
0 / 5 - 0 ratings