Skip to content

Missed optimization opportunity when Vec::push follows Vec::pop #114334

Closed
@krtab

Description

@krtab

The following code

pub fn noop(v: &mut Vec<u8>) {
    if let Some(x) = v.pop() {
        v.push(x)
    }
}

should be optimized to a no-op afaict, but instead gives:

example::noop:
        push    rbp
        push    rbx
        push    rax
        mov     rcx, qword ptr [rdi + 16]
        test    rcx, rcx
        je      .LBB2_4
        mov     rbx, rdi
        lea     rsi, [rcx - 1]
        mov     qword ptr [rdi + 16], rsi
        mov     rax, qword ptr [rdi]
        movzx   ebp, byte ptr [rax + rcx - 1]
        cmp     rsi, qword ptr [rdi + 8]
        jne     .LBB2_3
        mov     rdi, rbx
        call    alloc::raw_vec::RawVec<T,A>::reserve_for_push
        mov     rax, qword ptr [rbx]
        mov     rsi, qword ptr [rbx + 16]
.LBB2_3:
        mov     byte ptr [rax + rsi], bpl
        inc     rsi
        mov     qword ptr [rbx + 16], rsi
.LBB2_4:
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret

(this is true on stable, beta and nightly).

I think a blocking point here is that rustc/LLVM do not manage to infer the invariant:

pub fn noop(v: &mut Vec<u8>) {
     // v.len <= v.capacity
     if let Some(x) = v.pop() {
        // v.len < v.capacity
        v.push(x)
    }
}

Note that the version with added asserts for this invariant removes the useless conditional call to reserve_for_push but instead gets useless calls to assert failure code.

pub fn noop(v: &mut Vec<u8>) {
    assert!(v.len() <= v.capacity());
    if let Some(x) = v.pop() {
        assert!(v.len() < v.capacity());
        v.push(x)
    }
}
example::noop:
        push    rax
        mov     rcx, qword ptr [rdi + 8]
        mov     rax, qword ptr [rdi + 16]
        cmp     rax, rcx
        ja      .LBB0_6
        test    rax, rax
        je      .LBB0_4
        lea     rdx, [rax - 1]
        mov     qword ptr [rdi + 16], rdx
        cmp     rdx, rcx
        jae     .LBB0_5
        mov     qword ptr [rdi + 16], rax
.LBB0_4:
        pop     rax
        ret
.LBB0_6:
        lea     rdi, [rip + .L__unnamed_1]
        lea     rdx, [rip + .L__unnamed_2]
        mov     esi, 41
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2
.LBB0_5:
        lea     rdi, [rip + .L__unnamed_3]
        lea     rdx, [rip + .L__unnamed_4]
        mov     esi, 40
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2

.L__unnamed_3:
        .ascii  "assertion failed: v.len() < v.capacity()"

.L__unnamed_5:
        .ascii  "/app/example.rs"

.L__unnamed_4:
        .quad   .L__unnamed_5
        .asciz  "\017\000\000\000\000\000\000\000\025\000\000\000\t\000\000"

.L__unnamed_1:
        .ascii  "assertion failed: v.len() <= v.capacity()"

.L__unnamed_2:
        .quad   .L__unnamed_5
        .asciz  "\017\000\000\000\000\000\000\000\023\000\000\000\005\000\000"

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.I-slowIssue: Problems and improvements with respect to performance of generated code.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions