I am trying to better understand when Rust performs bound checking and how one would know for certain whether the compiler is able to optimize certain parts away.
pub fn get_unchecked_mut(a: i32) -> Vec<i32>{
let buffers = &mut vec![1, 2, 3];
let mut i = a as usize;;
while i < buffers.len() {
unsafe {
let buf = buffers.get_unchecked_mut(i);
*buf = a;
};
i = 1;
}
return buffers.to_vec();
}
#[inline(never)]
pub fn normal_slice_index(a: i32) -> Vec<i32> {
let buffers = &mut vec![1, 2, 3];
let mut i = a as usize;;
while i < buffers.len() {
buffers[i] = a;
i = 1;
}
return buffers.to_vec();
}
pub fn iter_mut(a: i32) ->Vec<i32> {
let buffers = &mut vec![1, 2, 3];
let i = a as usize;
for buf in buffers[i..].iter_mut() {
*buf = a;
}
return buffers.to_vec();
}
Which results in the following Assembly:
core::ptr::drop_in_place<alloc::vec::Vec<i32>>:
mov rax, qword ptr [rdi 8]
test rax, rax
je .LBB0_2
mov ecx, 4
mul rcx
test rax, rax
je .LBB0_2
mov rdi, qword ptr [rdi]
mov edx, 4
mov rsi, rax
jmp qword ptr [rip __rust_dealloc@GOTPCREL]
.LBB0_2:
ret
.LCPI1_0:
.quad 3
.quad 3
example::get_unchecked_mut:
push rbp
push r14
push rbx
sub rsp, 32
mov ebp, esi
mov r14, rdi
mov edi, 12
mov esi, 4
call qword ptr [rip __rust_alloc@GOTPCREL]
test rax, rax
je .LBB1_12
mov rbx, rax
movabs rax, 8589934593
mov qword ptr [rbx], rax
mov dword ptr [rbx 8], 3
mov qword ptr [rsp 8], rbx
movaps xmm0, xmmword ptr [rip .LCPI1_0]
movups xmmword ptr [rsp 16], xmm0
cmp ebp, 2
ja .LBB1_5
mov eax, ebp
add dword ptr [rbx 4*rax], ebp
cmp ebp, 2
jae .LBB1_5
add dword ptr [rbx 4*rax 4], ebp
test ebp, ebp
jne .LBB1_5
add dword ptr [rbx 4*rax 8], ebp
.LBB1_5:
mov edi, 12
mov esi, 4
call qword ptr [rip __rust_alloc@GOTPCREL]
test rax, rax
je .LBB1_6
mov qword ptr [r14], rax
mov ecx, dword ptr [rbx 8]
mov dword ptr [rax 8], ecx
mov rcx, qword ptr [rbx]
mov qword ptr [rax], rcx
movaps xmm0, xmmword ptr [rip .LCPI1_0]
movups xmmword ptr [r14 8], xmm0
mov esi, 12
mov edx, 4
mov rdi, rbx
call qword ptr [rip __rust_dealloc@GOTPCREL]
mov rax, r14
add rsp, 32
pop rbx
pop r14
pop rbp
ret
.LBB1_12:
mov edi, 12
mov esi, 4
call qword ptr [rip alloc::alloc::handle_alloc_error@GOTPCREL]
ud2
.LBB1_6:
mov edi, 12
mov esi, 4
call qword ptr [rip alloc::alloc::handle_alloc_error@GOTPCREL]
ud2
mov rbx, rax
lea rdi, [rsp 8]
call core::ptr::drop_in_place<alloc::vec::Vec<i32>>
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
call qword ptr [rip core::panicking::panic_no_unwind@GOTPCREL]
ud2
.LCPI2_0:
.quad 3
.quad 3
example::normal_slice_index:
push rbp
push r14
push rbx
sub rsp, 32
mov ebp, esi
mov r14, rdi
mov edi, 12
mov esi, 4
call qword ptr [rip __rust_alloc@GOTPCREL]
test rax, rax
je .LBB2_12
mov rbx, rax
movabs rax, 8589934593
mov qword ptr [rbx], rax
mov dword ptr [rbx 8], 3
mov qword ptr [rsp 8], rbx
movaps xmm0, xmmword ptr [rip .LCPI2_0]
movups xmmword ptr [rsp 16], xmm0
cmp ebp, 2
ja .LBB2_5
mov eax, ebp
add dword ptr [rbx 4*rax], ebp
cmp ebp, 2
jae .LBB2_5
add dword ptr [rbx 4*rax 4], ebp
test ebp, ebp
jne .LBB2_5
add dword ptr [rbx 4*rax 8], ebp
.LBB2_5:
mov edi, 12
mov esi, 4
call qword ptr [rip __rust_alloc@GOTPCREL]
test rax, rax
je .LBB2_6
mov qword ptr [r14], rax
mov ecx, dword ptr [rbx 8]
mov dword ptr [rax 8], ecx
mov rcx, qword ptr [rbx]
mov qword ptr [rax], rcx
movaps xmm0, xmmword ptr [rip .LCPI2_0]
movups xmmword ptr [r14 8], xmm0
mov esi, 12
mov edx, 4
mov rdi, rbx
call qword ptr [rip __rust_dealloc@GOTPCREL]
mov rax, r14
add rsp, 32
pop rbx
pop r14
pop rbp
ret
.LBB2_12:
mov edi, 12
mov esi, 4
call qword ptr [rip alloc::alloc::handle_alloc_error@GOTPCREL]
ud2
.LBB2_6:
mov edi, 12
mov esi, 4
call qword ptr [rip alloc::alloc::handle_alloc_error@GOTPCREL]
ud2
mov rbx, rax
lea rdi, [rsp 8]
call core::ptr::drop_in_place<alloc::vec::Vec<i32>>
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
call qword ptr [rip core::panicking::panic_no_unwind@GOTPCREL]
ud2
.LCPI3_0:
.quad 3
.quad 3
example::iter_mut:
push rbp
push r14
push rbx
sub rsp, 32
mov ebp, esi
mov r14, rdi
mov edi, 12
mov esi, 4
call qword ptr [rip __rust_alloc@GOTPCREL]
test rax, rax
je .LBB3_22
mov rbx, rax
movabs rax, 8589934593
mov qword ptr [rbx], rax
mov dword ptr [rbx 8], 3
mov qword ptr [rsp 8], rbx
movdqa xmm0, xmmword ptr [rip .LCPI3_0]
movdqu xmmword ptr [rsp 16], xmm0
movsxd rdi, ebp
cmp edi, 4
jae .LBB3_2
cmp ebp, 3
je .LBB3_16
lea rax, [rbx 4*rdi]
lea rdx, [4*rdi]
mov ecx, 8
sub rcx, rdx
cmp rcx, 28
jb .LBB3_14
shr rcx, 2
add rcx, 1
mov r8, rcx
and r8, -8
movd xmm0, ebp
pshufd xmm0, xmm0, 0
lea rdx, [r8 - 8]
mov r9, rdx
shr r9, 3
add r9, 1
test rdx, rdx
je .LBB3_7
mov rsi, r9
and rsi, -2
lea rdx, [rbx 4*rdi]
add rdx, 48
xor edi, edi
.LBB3_9:
movdqu xmm1, xmmword ptr [rdx 4*rdi - 48]
movdqu xmm2, xmmword ptr [rdx 4*rdi - 32]
movdqu xmm3, xmmword ptr [rdx 4*rdi - 16]
movdqu xmm4, xmmword ptr [rdx 4*rdi]
paddd xmm1, xmm0
paddd xmm2, xmm0
movdqu xmmword ptr [rdx 4*rdi - 48], xmm1
movdqu xmmword ptr [rdx 4*rdi - 32], xmm2
paddd xmm3, xmm0
paddd xmm4, xmm0
movdqu xmmword ptr [rdx 4*rdi - 16], xmm3
movdqu xmmword ptr [rdx 4*rdi], xmm4
add rdi, 16
add rsi, -2
jne .LBB3_9
test r9b, 1
je .LBB3_12
.LBB3_11:
movdqu xmm1, xmmword ptr [rax 4*rdi]
movdqu xmm2, xmmword ptr [rax 4*rdi 16]
paddd xmm1, xmm0
paddd xmm2, xmm0
movdqu xmmword ptr [rax 4*rdi], xmm1
movdqu xmmword ptr [rax 4*rdi 16], xmm2
.LBB3_12:
cmp rcx, r8
je .LBB3_16
lea rax, [rax 4*r8]
.LBB3_14:
mov rcx, rbx
add rcx, 12
.LBB3_15:
add dword ptr [rax], ebp
add rax, 4
cmp rax, rcx
jne .LBB3_15
.LBB3_16:
mov edi, 12
mov esi, 4
call qword ptr [rip __rust_alloc@GOTPCREL]
test rax, rax
je .LBB3_17
mov qword ptr [r14], rax
mov ecx, dword ptr [rbx 8]
mov dword ptr [rax 8], ecx
mov rcx, qword ptr [rbx]
mov qword ptr [rax], rcx
movaps xmm0, xmmword ptr [rip .LCPI3_0]
movups xmmword ptr [r14 8], xmm0
mov esi, 12
mov edx, 4
mov rdi, rbx
call qword ptr [rip __rust_dealloc@GOTPCREL]
mov rax, r14
add rsp, 32
pop rbx
pop r14
pop rbp
ret
.LBB3_7:
xor edi, edi
test r9b, 1
jne .LBB3_11
jmp .LBB3_12
.LBB3_22:
mov edi, 12
mov esi, 4
call qword ptr [rip alloc::alloc::handle_alloc_error@GOTPCREL]
ud2
.LBB3_2:
lea rdx, [rip .L__unnamed_1]
mov esi, 3
call qword ptr [rip core::slice::index::slice_start_index_len_fail@GOTPCREL]
jmp .LBB3_3
.LBB3_17:
mov edi, 12
mov esi, 4
call qword ptr [rip alloc::alloc::handle_alloc_error@GOTPCREL]
.LBB3_3:
ud2
mov rbx, rax
lea rdi, [rsp 8]
call core::ptr::drop_in_place<alloc::vec::Vec<i32>>
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
call qword ptr [rip core::panicking::panic_no_unwind@GOTPCREL]
ud2
.L__unnamed_2:
.ascii "/app/example.rs"
.L__unnamed_1:
.quad .L__unnamed_2
.asciz "\017\000\000\000\000\000\000\000!\000\000\000\020\000\000"
DW.ref.rust_eh_personality:
.quad rust_eh_personality
For these 3 scenarios, when does Rust perform bound checking and what does this look like in Assembly?
The get_unchecked_mut()
is guaranteed to not do any bounds checking, but does it occur for the latter two? I would presume it does, but I can not find it anywhere in the ASM.
CodePudding user response:
In normal_slice_index
the compiler simply unrolled the loop and did the elements separately. The bounds checking is on lines 112-113:
cmp ebp, 2
ja .LBB2_5
Since you explicitly have while i < buffers.len()
there will be no error.
In iter_mut
the bounds checking is on lines 187-188:
cmp edi, 4
jae .LBB3_2
At .LBB3_3
we have:
mov esi, 3
call qword ptr [rip core::slice::index::slice_start_index_len_fail@GOTPCREL]
Which will raise the error.
Note that since you have the vec
visible to the compiler, it knows the length is 3 and that got inlined. Code might look different if you pass in the vector as an argument.