Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
matthiaskrgr opened this issue Sep 22, 2018 · 5 comments
Closed

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

matthiaskrgr opened this issue Sep 22, 2018 · 5 comments
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. I-slow Issue: Problems and improvements with respect to performance of generated code.

Comments

@matthiaskrgr
Copy link
Member

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.

@estebank estebank added I-slow Issue: Problems and improvements with respect to performance of generated code. A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) labels Sep 22, 2018
@cynecx
Copy link
Contributor

cynecx commented Sep 24, 2018

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

@oli-obk oli-obk added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. and removed A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) labels Jan 28, 2019
@matthiaskrgr
Copy link
Member Author

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

@tspiteri
Copy link
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

@RalfJung
Copy link
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.

@nikic
Copy link
Contributor

nikic commented Mar 13, 2021

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
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. I-slow Issue: Problems and improvements with respect to performance of generated code.
Projects
None yet
Development

No branches or pull requests

7 participants