Rust employs dynamic checking methods to check many things. One such example is bounds checking of arrays.
Take this code for example,
fn test_dynamic_checking() -> i32 {
let x = [1, 2, 3, 4];
x[1]
}
The resulting LLVM IR is:
; Function Attrs: uwtable
define internal i32 @_ZN10dynamic_ck21test_dynamic_checking17hcef32a1e8c339e2aE() unnamed_addr #0 {
entry-block:
%x = alloca [5 x i32]
%0 = bitcast [5 x i32]* %x to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %0, i8* bitcast ([5 x i32]* @const7091 to i8*), i64 20, i32 4, i1 false)
%1 = getelementptr inbounds [5 x i32], [5 x i32]* %x, i32 0, i32 0
%2 = call i1 @llvm.expect.i1(i1 false, i1 false)
br i1 %2, label %cond, label %next
next: ; preds = %entry-block
%3 = getelementptr inbounds i32, i32* %1, i64 1
%4 = load i32, i32* %3
ret i32 %4
cond: ; preds = %entry-block
call void @_ZN4core9panicking18panic_bounds_check17hcc71f10000bd8e6fE({ %str_slice, i32 }* noalias readonly dereferenceable(24) @panic_bounds_check_loc7095, i64 1, i64 5)
unreachable
}
A branch instruction is inserted to decide whether the index is out of bounds or not, which doesn't exist in clang-compiled LLVM IR.
Here are my questions:
Conceptually, Rust performs array bound checking on each and every array access. However, the compiler is very good at optimizing away the checks when it can prove that it's safe to do so.
The LLVM intermediate output is misleading because it still undergoes optimizations by the LLVM's optimizing machinery before the machine assembly is generated. A better way to inspect assembly output is by generating the final assembly using an invocation such as rustc -O --emit asm --crate-type=lib
. The assembly output for your function is just:
push rbp
mov rbp, rsp
mov eax, 2
pop rbp
ret
Not only is there no bound checking in sight, there is no array to begin with, the compiler has optimized the entire function to a return 2i32
! To force bound checking, the function needs to be written so that Rust cannot prove that it can be elided:
pub fn test_dynamic_checking(ind: usize) -> i32 () {
let x = [1, 2, 3, 4];
x[ind]
}
This results in a larger assembly, where the bound check is implemented as the following two instructions:
cmp rax, 3 ; compare index with 3
ja .LBB0_2 ; if greater, jump to panic code
That is as efficient as it gets. Turning off the bound check is rarely a good idea because it can easily cause the program to crash. It can be done, but explicitly and only within the confines of an unsafe
block or function:
pub unsafe fn test_dynamic_checking(ind: usize) -> i32 () {
let x = [1, 2, 3, 4];
*x.get_unchecked(ind)
}
The generated assembly shows the error checking to be entirely omitted:
push rbp
mov rbp, rsp
lea rax, [rip + const3141]
mov eax, dword ptr [rax + 4*rdi]
pop rbp
ret
const3141:
.long 1
.long 2
.long 3
.long 4
In what situations does Rust implement dynamic checking?
It's a bit of a cop out... but Rust, the language, does not implement any dynamic checking. The Rust libraries, however, starting with the core
and std
libraries do.
A library needs to use run-time checking whenever not doing so could lead to a memory safety issue. Examples include:
Index
RefCell
In general, the Rust philosophy is to perform as many checks as possible at compile-time, but some checks need by delayed to run-time because they depend on dynamic behavior/values.
How does Rust implement dynamic checking in different situations?
As efficiently as possible.
When dynamic checking is required, the Rust code will be crafted to be as efficient as possible. This can be slightly complicated, as it involves trying to ease the work of the optimizer and conforming to patterns that the optimizer recognizes (or fixing it), but we have the chance that a few developers are obsessed with performance (@bluss, for example).
Not all checks may be elided, of course, and those that remain will typically be just a branch.
Is there any way to turn off the dynamic checking?
Whenever dynamic checking is necessary to guarantee code safety, it is not possible to turn in off in safe code.
In some situations, though, an unsafe
block or function may allow to bypass the check (for example get_unchecked
for indexing).
This is not recommended in general, and should be a last-resort behaviour:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With