Skip to content

Failure to elide bound checks for multiple slice writes by index after length check that can't overflow #55147

Closed
@hsivonen

Description

@hsivonen

Nightly Rust Godbolt link.

These two functions should logically compile to the same code but don't:

pub fn unchecked(dst: &mut [u8], offset: usize) {
    let mut i = offset;
    if i.checked_add(4).unwrap() <= dst.len() {
        unsafe {
            *(dst.get_unchecked_mut(i)) = 1;
            i += 1;
            *(dst.get_unchecked_mut(i)) = 2;
            i += 1;
            *(dst.get_unchecked_mut(i)) = 3;
            i += 1;
            *(dst.get_unchecked_mut(i)) = 4;
        }
    }
}

pub fn checked(dst: &mut [u8], offset: usize) {
    let mut i = offset;
    if i.checked_add(4).unwrap() <= dst.len() {
        dst[i] = 1;
        i += 1;
        dst[i] = 2;
        i += 1;
        dst[i] = 3;
        i += 1;
        dst[i] = 4;
    } 
}

The output is:

example::unchecked:
        push    rax
        mov     rax, rdx
        add     rax, 4
        jb      .LBB0_4
        cmp     rax, rsi
        ja      .LBB0_3
        mov     dword ptr [rdi + rdx], 67305985
.LBB0_3:
        pop     rax
        ret
.LBB0_4:
        lea     rdi, [rip + .L__unnamed_1]
        call    qword ptr [rip + _ZN4core9panicking5panic17hd62333a8bd86ba63E@GOTPCREL]
        ud2

example::checked:
        push    rax
        mov     rcx, rdx
        add     rcx, 4
        jb      .LBB1_14
        mov     rax, rsi
        cmp     rcx, rsi
        ja      .LBB1_7
        cmp     rdx, rax
        jae     .LBB1_8
        mov     byte ptr [rdi + rdx], 1
        lea     rsi, [rdx + 1]
        cmp     rsi, rax
        jae     .LBB1_11
        mov     byte ptr [rdi + rdx + 1], 2
        lea     rsi, [rdx + 2]
        cmp     rsi, rax
        jae     .LBB1_12
        mov     byte ptr [rdi + rdx + 2], 3
        add     rdx, 3
        cmp     rdx, rax
        jae     .LBB1_13
        mov     byte ptr [rdi + rdx], 4
.LBB1_7:
        pop     rax
        ret
.LBB1_14:
        lea     rdi, [rip + .L__unnamed_1]
        call    qword ptr [rip + _ZN4core9panicking5panic17hd62333a8bd86ba63E@GOTPCREL]
        ud2
.LBB1_8:
        lea     rdi, [rip + .L__unnamed_2]
        mov     rsi, rdx
        mov     rdx, rax
        call    qword ptr [rip + _ZN4core9panicking18panic_bounds_check17h8f5d51613726af8dE@GOTPCREL]
        ud2
.LBB1_11:
        lea     rdi, [rip + .L__unnamed_3]
        mov     rdx, rax
        call    qword ptr [rip + _ZN4core9panicking18panic_bounds_check17h8f5d51613726af8dE@GOTPCREL]
        ud2
.LBB1_12:
        lea     rdi, [rip + .L__unnamed_4]
        mov     rdx, rax
        call    qword ptr [rip + _ZN4core9panicking18panic_bounds_check17h8f5d51613726af8dE@GOTPCREL]
        ud2
.LBB1_13:
        lea     rdi, [rip + .L__unnamed_5]
        mov     rsi, rdx
        mov     rdx, rax
        call    qword ptr [rip + _ZN4core9panicking18panic_bounds_check17h8f5d51613726af8dE@GOTPCREL]
        ud2

.L__unnamed_6:
        .ascii  "called `Option::unwrap()` on a `None` value"

.L__unnamed_7:
        .ascii  "libcore/option.rs"

.L__unnamed_1:
        .quad   .L__unnamed_6
        .asciz  "+\000\000\000\000\000\000"
        .quad   .L__unnamed_7
        .asciz  "\021\000\000\000\000\000\000\000c\001\000\000\025\000\000"

str.0:
        .ascii  "/tmp/compiler-explorer-compiler118917-56-2jgk8f.vziuf/example.rs"

.L__unnamed_2:
        .quad   str.0
        .quad   64
        .long   19
        .long   9

.L__unnamed_3:
        .quad   str.0
        .quad   64
        .long   21
        .long   9

.L__unnamed_4:
        .quad   str.0
        .quad   64
        .long   23
        .long   9

.L__unnamed_5:
        .quad   str.0
        .quad   64
        .long   25
        .long   9

That is, the safe function emits bound checks for each write via slice subscript despite there being enough information to decide that the bound checks cannot fail.

In encoding_rs the use of unsafe as seen in the first function is necessary for the UTF-16 to UTF-8 encoder to be competitive with C++.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.I-slowIssue: Problems and improvements with respect to performance of generated code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions