Skip to content

const eval Vec::new() the same as vec![] #54475

Closed

Description

@matthiaskrgr

For some reason, it seems that rustc can deduce .len() == 0 better for vec![] than for Vec::new().

pub fn a() -> usize {
    let v: Vec<u8> = Vec::new(); 
    let mut v2: Vec<u8> = Vec::new();

    if v.len() > 1 { // false
        v2 = vec![2];
    } else { 
        v2 = Vec::new();
    }
    v.len() + v2.len() // 0
}
example::a:
        push    rbx
        sub     rsp, 48
        xorps   xmm0, xmm0
        movaps  xmmword ptr [rsp + 32], xmm0
        mov     qword ptr [rsp + 8], 1
        movups  xmmword ptr [rsp + 16], xmm0
        mov     rsi, qword ptr [rsp + 16]
        mov     rbx, qword ptr [rsp + 24]
        test    rsi, rsi
        je      .LBB0_2
        mov     edi, 1
        mov     edx, 1
        call    __rust_dealloc@PLT
.LBB0_2:
        mov     rax, rbx
        add     rsp, 48
        pop     rbx
        ret

https://rust.godbolt.org/z/xEhPhI

surprisingly, when I replaced one Vec::new() with vec![], rustc was able to const-eval the return value to 0.

pub fn a() -> usize {
    let v: Vec<u8> = Vec::new(); 
    let mut v2: Vec<u8> = Vec::new();

    if v.len() > 1 { // false
        v2 = vec![2];
    } else { 
        v2 = vec![]; // I changed this
    }
    v.len() + v2.len() // 0
}
example::a:
        xor     eax, eax
        ret

https://rust.godbolt.org/z/cdbEGU

Apparently

pub fn a() -> usize {
    let v: Vec<u8> = vec![];
    v.len()
}

and

pub fn a() -> usize {
    let v: Vec<u8> = Vec::new();
    v.len()
}

generate the same code tough.

Activity

  1. added
    I-slowIssue: Problems and improvements with respect to performance of generated code.
    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)
    on Sep 22, 2018
  2. cynecx commented on Sep 24, 2018

    @cynecx
    Contributor

    This may be a limitation with llvm's alias analysis (or other components). Does rust perhaps emit suboptimal llvm-ir?:

    (reduced test-case)

    target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
    target triple = "x86_64-unknown-linux-gnu"
    
    %VecU8 = type { [0 x i64], { i8*, i64 }, [0 x i64], i64, [0 x i64] }
    
    define i64 @_ZN7example1a17haca38bae68eb7066E() {
      %v2 = alloca %VecU8, align 8
      %1 = bitcast %VecU8* %v2 to i8**
      %2 = getelementptr inbounds %VecU8, %VecU8* %v2, i64 0, i32 1, i32 1
      %3 = bitcast i64* %2 to i8*
      
      ; `buf` is a 16-bytes, "zero-initialized" by a memset, array
      %buf = alloca [16 x i8], align 8
      %buf_ptr = getelementptr inbounds [16 x i8], [16 x i8]* %buf, i64 0, i64 0
      call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %buf_ptr, i8 0, i64 16, i1 false)
      
      store i8* inttoptr (i64 1 to i8*), i8** %1, align 8
      
      ; "zero-initializes" the two i64 (skipping the i8*) of %v2 through %2 by copying from %buf which is "known" to be zero
      call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 8 %3, i8* nonnull align 8 %buf_ptr, i64 16, i1 false)
      
      ; llvm can't prove that the i64 at %v2.idx has been zero-initialized by the memcpy
      %v2.idx = getelementptr inbounds %VecU8, %VecU8* %v2, i64 0, i32 3
      %v2.idx.val = load i64, i64* %v2.idx, align 8
      
      ret i64 %v2.idx.val
    }
    
    declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i1)
    declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1)

    https://godbolt.org/z/-IvKEW

  3. added
    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.
    and removed
    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)
    on Jan 28, 2019
  4. matthiaskrgr commented on Dec 12, 2019

    @matthiaskrgr
    MemberAuthor

    Note that making Vec::new() a const fn did not fix the issue.

  5. tspiteri commented on Mar 31, 2020

    @tspiteri
    Contributor

    If you create a const Vec and use it instead of Vec::new(), it does seem to work around the issue.

    pub fn a() -> usize {
        let v: Vec<u8> = Vec::new(); 
        let mut v2: Vec<u8> = Vec::new();
    
        if v.len() > 1 { // false
            v2 = vec![2];
        } else { 
            const NEW: Vec<u8> = Vec::new();
            v2 = NEW;
        }
        v.len() + v2.len() // 0
    }
    example::a:
            xor     eax, eax
            ret

    https://rust.godbolt.org/z/ftbvHX

  6. RalfJung commented on Mar 31, 2020

    @RalfJung
    Member

    Note that making Vec::new() a const fn did not fix the issue.

    And unsurprisingly so -- making a function const fn has exactly no effect at all on the generated LLVM IR.

  7. nikic commented on Mar 13, 2021

    @nikic
    Contributor

    The Vec::new() variant optimizes well on nightly, presumably as a result of the LLVM 12 upgrade. I'm not sure which upstream change is responsible in this case though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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