The code generated when comparing a signed non-zero type to -1 or 1 varies wildly, and appears to generate suboptimal branching code.
A more in-depth analysis of the generated assembly can be found here: https://godbolt.org/z/qxzGrv9nn.
Basic
Looking at these two functions, one would expect them to generate the same assembly.
pub fn is_one_1(len: NonZeroI32) -> bool {
matches!(len.get(), -1 | 1)
}
pub fn is_one_2(len: NonZeroI32) -> bool {
len.get() == -1 || len.get() == 1
}
However that is not the case, with is_one_1 in fact branching as seen below. Whilst I have not run benchmarks to see if branching is more performant, one would expect it not to be.
example::is_one_1:
cmp edi, 1
je .LBB0_3
cmp edi, -1
jne .LBB0_2
.LBB0_3:
mov al, 1
ret
.LBB0_2:
xor eax, eax
ret
example::is_one_2:
cmp edi, -1
sete cl
cmp edi, 1
sete al
or al, cl
ret
What if we include 0 in the comparison?
Since len is a NonZeroI32, it is guaranteed that len.get() != 0 otherwise it is undefined behaviour. This means that the function below should behave the same as is_one_1.
pub fn is_one_3(len: NonZeroI32) -> bool {
matches!(len.get(), -1 | 0 | 1)
}
In fact the generated assembly for this is the smallest yet.
example::is_one_3:
add edi, 1
cmp edi, 3
setb al
ret
This assembly is actually also generated when we include 0 in the comparison in the style of is_one_2.
pub fn is_one_4(len: NonZeroI32) -> bool {
len.get() == -1 || len.get() == 0 || len.get() == 1
}
example::is_one_4:
add edi, 1
cmp edi, 3
setb al
ret
Expected behaviour
The expected behaviour of the compiler would be to generate assembly that matches between the different implementations of this check.
The code generated when comparing a signed non-zero type to
-1or1varies wildly, and appears to generate suboptimal branching code.A more in-depth analysis of the generated assembly can be found here: https://godbolt.org/z/qxzGrv9nn.
Basic
Looking at these two functions, one would expect them to generate the same assembly.
However that is not the case, with
is_one_1in fact branching as seen below. Whilst I have not run benchmarks to see if branching is more performant, one would expect it not to be.What if we include
0in the comparison?Since
lenis aNonZeroI32, it is guaranteed thatlen.get() != 0otherwise it is undefined behaviour. This means that the function below should behave the same asis_one_1.In fact the generated assembly for this is the smallest yet.
This assembly is actually also generated when we include
0in the comparison in the style ofis_one_2.Expected behaviour
The expected behaviour of the compiler would be to generate assembly that matches between the different implementations of this check.