Consider the following code:
#[inline(never)]
fn black_box<T: std::fmt::Display>(x: T) { println!("{}", x); }
pub fn foo_i32(n: usize) {
let mut x = 0i32;
for _ in 0..n {
black_box(x);
x = (x+5) % 16;
}
}
pub fn foo_u32(n: usize) {
let mut x = 0u32;
for _ in 0..n {
black_box(x);
x = (x+5) % 16;
}
}
As shown by Godbolt (with -C opt-level=3
), the assembly for modifying x
in foo_u32
is:
add ebp, 5 ; bp += 5
and ebp, 15 ; bp &= 15
Which essentially translates to x = (x+5) & 15
. Great, this is probably as optimal as it could possibly be!
However, the assembly for foo_i32
is:
lea eax, [rbx + 5] ; ax = bx+5;
lea ecx, [rbx + 20] ; cx = bx+20;
test eax, eax
cmovns ecx, eax ; if ax >= 0 { cx = ax; }
and ecx, -16 ; cx &= -16; (i.e. cx &= ~15)
neg ecx ; cx *= -1;
add ebx, ecx ; bx += cx;
add ebx, 5 ; bx += 5;
Which essentially translates to:
x+5 - ((if x+5 >= 0 { x+5 } else { x+20 }) & -16)
However, in the case that x+5 >= 0
, it's the same as x+5 - ((x+5) & -16)
, and x+5 - ((x+5) & ~15)
, and (x+5) & 15
which is exactly what the assembly for foo_u32
does above. In other words, the only difference is that the assembly for foo_i32
needlessly handles the unreachable case where x < 0
, and the instruction count suffers as a result.
Shockingly, if you just give the compiler a nudge, it literally compiles to the exact same assembly as the unsigned version, completely eliding any panics!
pub fn foo_i32(n: usize) {
let mut x = 0i32;
for _ in 0..n {
black_box(x);
if (x+5 < 0) { unreachable!() } // !!!
x = (x+5) % 16;
}
}
Literally, you don't even need to do unreachable_unchecked()
, just regular unreachable!()
does the trick, which can only mean that the compiler is able to prove that x+5 >= 0
... but only if prompted?!?! Perhaps this is due to some sort of search depth limit or time limit in the optimizer, in which case maybe that limit should be relaxed?
Anyway, the bottom line is that this seems like a missed optimization to me; even though the compiler could (and clearly knows how to!!) optimize the operation, it normally doesn't.