基本分支判断
分支结构是程序中基础、实用且频繁出现的模块,即通过判断条件结果选择不同的程序向前路径以实现不同的功能逻辑。
- B.cond系列
在Arm64汇编指令中,有一个CPSR寄存器称之为程序状态寄存器,有N\Z\C\V四个标志位存在其中,ARM汇编主要通过这些标志位的标记和判断实现分支结构的功能,其基本用法结构:
CMP/CCMP/ADDS/SUBS/ANDS/TST… … //改写状态寄存器的指令
B.cond label //条件状态判断,满足则跳转至label所在位置
B为跳转指令,Cond表示跳转指令的条件代码后缀,不同标志表示了跳转需要满足的不同条件,具体如表1所示。B后面不跟随Cond标志,则表示无条件跳转至label地址。
表1 状态标志位表 编码
助记符
描述
标记
0000
EQ
运算结果相等为1
Z==1
0001
NE
运算结果不等为0
Z==0
0010
HS/CS
无符号高或者相同进位,发生进位为1
C==1
0011
LO/CC
无符号低清零,发生借位为0
C==0
0100
MI
负数为1
N==1
0101
PL
非负数为0
N==0
0110
VS
有符号溢出为1
V==1
0111
VC
没有溢出为0
V==0
1000
HI
无符号>
C==1 && Z==0
1001
LS
无符号<=
!( C==1 && Z==0)
1010
GE
带符号>=
N==V
1011
LT
带符号<
N!=V
1100
GT
带符号>
Z==0 && N==V
1101
LE
带符号<=
!( Z==0 && N==V)
1110
AL
无条件执行
Any
1111
NV
下面表格更直观显示了常见C实现的分支结构如何用Arm64汇编进行替代:
条件判断类型
C程序实现
汇编实现
单一条件判断
/* Assumptions: Cond A: a > b */
If (Cond A){
Part 1
} else {
Part 2
}
End
/* Assume the values of a and b are stored in registers x1 and x2. Note that value of b can be a variable loaded from its address or a constant. */
ldr x1, [a_addr]
ldr x2, [b_addr]
cmp x1, x2
b.le Part2
(Part1:)
... ...
b End
Part2:
... ...
End:
… …
双重条件判断
/* Assumptions: Cond A: a > b \
Cond B: a > c */
If (Cond A && Cond B){
Part 1
} else {
Part 2
}
End
/* Assume the values of a, b and c are stored in registers x1, x2 and x3. */
ldr x1, [a_addr]
ldr x2, [b_addr]
ldr x3, [c_addr]
cmp x1, x2
ccmp x1, x3, 0, hi
b.hi Part1
Part2:
... ...
b End
Part1:
... ...
End
- 特殊判断指令
通过B.cond可以满足所有的判断逻辑,但有时候对于一些简单的判断和特殊情况的判断,Arm64提供了部分指令来支持,如下表所示。
指令
使用范例
功能描述
CBZ
CBZ X1, label
如果x1 == 0,则跳转到label
CBNZ
CBNZ X1, label
如果x1 != 0,则跳转到label
TBZ
TBZ X1, #3, label
如果x1寄存器的第三位等于0,则跳转到label
TBNZ
TBNZ X1, #3, label
如果x1寄存器的第三位不等于0,则跳转到label
这里我们特别来想象一下TBZ/TBNZ的使用场景。指令功能仅描述了其可以用于判断某个值的某位是否等于0,结合计算机用保存数据的最小单位是bit,可以进一步判断该值与2n值之间的关系。
C程序实现
汇编实现
/* No assumption */
if (x1 >= 64) {
part1
} else if (x1 >= 32) {
part2
} else if (x1 >= 16) {
part3
} else {
part 4
}
tbnz x1, #4, Part3 /* over16 */
... ...
b end
Part3:
tbnz x1, #5, Part2 /* over32 */
... ...
b end
Part2:
tbnz x1, #6, Part1 /* over64 */
... ...
b end
Part1:
... ...
end:
... ...
/* Assumption: x1 < 128 */
if (x1 >= 64) {
part1
} else if (x1 >= 32) {
part2
} else if (x1 >= 16) {
part3
} else {
part 4
}
/* Ensure the x1 is less than 128, which means the bit value higher that 6th bit equals 0. */
cmp x1, 128
b.ge end
tbz x1, #6, Part2 /* 64less */
... ...
b end
Part2:
tbz x1, #5, Part3 /* 32less */
... ...
b end
Part3:
tbz x1, #4, Part4 /* 16less */
... ...
b end
Part4:
... ...
end:
... ...
如上述代码所示,TBNZ可用于连续判断进入条件值大于等于2n的分支,而TBZ可用于连续判断进入条件值小于2n的分支。需要注意的是,TBZ判断某一位为0时,并不意味着该值就一定小于2n,有可能高位存在1而使整个值更大,所以在使用TBZ判断时,首先应该限制条件值的范围小于2n+1。
- 判断分支结果选择指令CSEL
分支结构的增加是因为不同情况下需要处理不同的过程,一般都会引入判断指令和跳转指令进而可能影响流水线顺畅程度。但当这个过程相对比较简单而且最后通过赋值到某些寄存器作为结束,那么可以考虑使用CSEL指令,避免了分支跳转。其最简单的应该就类似于三目运算符,如下表所示。
C程序实现
汇编实现
int a = 3;
int b = 4;
int c = (a > b) ? a : b;
/* Assume the values of a and b stored in addr_a and addr_b, and the values of c will be stored in addr_c*/
... ...
ldr vala, [addr_a]
ldr valb, [addr_b]
cmp vala, valb
csel valc, vala, valb, ge
str valc, [addr_c]
... ...