Skip to content
New issue

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

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

Already on GitHub? # to your account

Inefficient array initialization when moving data into an Option #84426

Open
jordens opened this issue Apr 22, 2021 · 0 comments
Open

Inefficient array initialization when moving data into an Option #84426

jordens opened this issue Apr 22, 2021 · 0 comments
Labels
A-array Area: `[T; N]` C-bug Category: This is a bug.

Comments

@jordens
Copy link

jordens commented Apr 22, 2021

I tried this code:

#![no_std]

fn edit(d: &mut [u8]) {
    d[40] = 1;
    d[80] = 1;
    d[120] = 1;
    d[160] = 1;
}

// Returning the array is fine
pub fn array_good() -> [u8; 200] {
    let mut d = [0u8; 200];
    edit(&mut d);
    d
}

// Setting the bytes within the option is also fine
pub fn option_good() -> Option<[u8; 200]> {
    let mut o = Some([0u8; 200]);
    if let Some(ref mut d) = o {
        edit(d);
    }
    o
}

// When returning an initialized array in an Option,
// the initialization gets split into multiple separate memset/memclr calls,
// just to optimize away a few redundant byte clears.
pub fn option_bad() -> Option<[u8; 200]> {
    Some(array_good())
}

On godbolt: https://godbolt.org/z/e8z3ornos

I expected to see this happen: In all three cases I expect roughly similar code with a single call to memclr followed by four stores.

Instead, this happened: The first implementation splits the initialization into five different calls to memclr. This is unnecessary and inefficient.

example::array_good:
        push    {r4, r6, r7, lr}
        add     r7, sp, #8
        movs    r1, #200
        mov     r4, r0
        bl      __aeabi_memclr
        movs    r0, #1
        strb.w  r0, [r4, #160]
        strb.w  r0, [r4, #120]
        strb.w  r0, [r4, #80]
        strb.w  r0, [r4, #40]
        pop     {r4, r6, r7, pc}

example::option_good:
        push    {r4, r6, r7, lr}
        add     r7, sp, #8
        mov     r4, r0
        adds    r0, #1
        movs    r1, #200
        bl      __aeabi_memclr
        movs    r0, #1
        strb.w  r0, [r4, #161]
        strb.w  r0, [r4, #121]
        strb.w  r0, [r4, #81]
        strb.w  r0, [r4, #41]
        strb    r0, [r4]
        pop     {r4, r6, r7, pc}

example::option_bad:
        push    {r4, r6, r7, lr}
        add     r7, sp, #8
        mov     r4, r0
        adds    r0, #1
        movs    r1, #40
        bl      __aeabi_memclr
        add.w   r0, r4, #42
        movs    r1, #39
        bl      __aeabi_memclr
        add.w   r0, r4, #82
        movs    r1, #39
        bl      __aeabi_memclr
        add.w   r0, r4, #122
        movs    r1, #39
        bl      __aeabi_memclr
        add.w   r0, r4, #162
        movs    r1, #39
        bl      __aeabi_memclr
        movs    r0, #1
        strb.w  r0, [r4, #161]
        strb.w  r0, [r4, #121]
        strb.w  r0, [r4, #81]
        strb.w  r0, [r4, #41]
        strb    r0, [r4]
        pop     {r4, r6, r7, pc}

The behavior is not target specific. The behavior is the same on x86, for larger arrays and initialized segments it will also call memset.

-C opt-level=1 doesn't inline and thus suppresses the issue.

Meta

rustc --version --verbose:

rustc 1.53.0-nightly (b84932674 2021-04-21)

This behavior is present between 1.45.0 and current nightly. Before 1.45.0 all three implementations split the initialization.

I noticed this when looking at #83022 (comment)

@jordens jordens added the C-bug Category: This is a bug. label Apr 22, 2021
@workingjubilee workingjubilee added the A-array Area: `[T; N]` label Mar 7, 2023
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-array Area: `[T; N]` C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

2 participants