Wed Apr 12 2023
Chapter 1.18.6 has a simple exercise, remove all the conditional jumps and replace them with the CSEL instruction in the following function:
f:
cmp x0, 10
; branch if equal
beq .L3 ; "it is ten"
adrp x0, .LC1 add x0, x0, :lo12:.LC1
ret
.L3:
; "it is not ten"
adrp x0, .LC0 add x0, x0, :lo12:.LC0
ret
.LC0:
"it is ten"
.string .LC1:
"it is not ten" .string
C source:
const char* f (int a)
{return a==10 ? "it is ten" : "it is not ten";
};
The reason for removing conditional jumps is because they are a form of branching. Modern CPUs attempts to guess the outcome of conditional operations by a technique called branch prediction. If the prediction is wrong the prediction CPU cycles are wasted, therefore we want to avoid the conditional jumps.
ARMs developer docs explains that CSEL has the following form[0]:
CSEL Xd, Xn, Xm, cond
and function:
if cond then
= Xn
Xd
else
= Xm Xd
Thus we can simply rewrite the function by putting pointers to the strings “it is ten string” and “it is not ten” in the x1 and x2 registers, and if the comparison with the function argument (a) and 10 is equal we put x1 in x0 else we put x2 in x0. Then we get:
.globl ff:
cmp x0, 10
adrp x1, .LC0
adrp x2, .LC1add x1,x1, :lo12:.LC0
add x2,x2, :lo12:.LC1
csel x0, x1, x2, EQret
.LC0:
"it is ten"
.string .LC1:
"it is not ten" .string
Which we can write a short main function to test with
#include <stdio.h>
extern const char* f (int a);
int main(void) {
"%s\n", f(10));
printf("%s\n", f(9));
printf(return 0;
}
It works!
localhost:~# gcc -c ex1.18.6_main.c f.s && gcc ex1.18.6_main.o f.o -o ex1.18.6 && ./ex1.18.6
it is ten
it is not ten
localhost:~#
[0]: https://developer.arm.com/documentation/102374/0101/Program-flow—conditional-select-instructions