条件操作码
条件标志位 | 描述 |
---|---|
N | 负数标志(上一次运算结果为负值) |
Z | 零结果标志(上一次运算结果为零) |
C | 进位标志(上一次运算结果发生了无符号溢出) |
V | 溢出标志(上一次运算结果发生了有符号溢出) |
加法指令
add x0, x1, #1 //把x1寄存器的值加过加上立即数 1,结果写进x0寄存器中
add x0, x1, #1, LSL 12 //把立即数 1 算数左移 12 位,然后再加 x1 寄存器的值,结果写入 x0 寄存器中
注意:立即数是一个无符号的,取值范围为 0~4095
add x0, x1, x2 //x0=x1+x2
add x0, x1,x2, LSL 2 //x0=x1+(x2 << 2)
mov x1, #1
mov x2, #0x8a
add x0, x1, x2, UXTB
//运行结果是 0x8B,UXTB 对寄存器 x2 进行无符号扩展,结果为0x8a,再加上 X1 寄存器的值,x0最终结果为0x8b
add x0, x1, x2, SXTB
// SXTB 对 x2 寄存器的值低8位进行有符号扩展,结果为0xFFFFFFFFFFFFFF8A,然后再加上 X1 寄存器的值,x0最终结果为0xFFFFFFFFFFFFFF8B
移位操作的加法指令
add x0, x1, x2, LSl, 3 //x0=x1+(x2<<3))
注意,移位的取值范围 0~63
- ADDS 指令
adds 指令是 add 指令的变种,它们的区别是指令执行结果会影响 PSTATE 寄存器的 N、Z、C、V 标志位,例如当计算结果发生无符号数溢出时,C=1 。
mov x1, 0xffffffffffffffff
adds x0, x1, #2
mrs x2, nxcv
x1 的值(0xffffffffffffffff)加上立即数 2 一定会触发无符号溢出,最终 X0 寄存器的值为 1,同时还设置 PSTATE 寄存器的 C 标志位为 1,我们可以通过读取 NZCV 寄存器来判断,最终 X2 寄存器的值为 0x20000000 ,说明第 29 位的 C (进位标志)字段置 1,
- ADC xd,xn, xm //Xd寄存器的值等于 Xn 寄存器的值加上 Xm 寄存器的值加上 C ,C表示 PSTATE 寄存器的 C 标志位。
mov x1, oxffffffffffffffff
mov x2, #2
adc x0, x1, x2
mrs x3, nzcv
ADC 指令计算过程: 0xFFFFFFFFFFFFFFFF + 2 + C ,因为 0xFFFFFFFFFFFFFFFF + 2 的过程中已经出发了无符号溢出,C=1 ,所以最终计算 X0 寄存器的值为 2,如果读取 NZCV 寄存器,发现 C 标志位也被置为 1 了。
SUB 指令
减法指令
sub x0, x1, #1 //把 x1 寄存器的值减去立即数 1,结果写入 x0 寄存器
//把立即数1算数左移12位,然后把x1寄存器中的值减去(1<<12),把结果写入 x0 寄存器中
sub x0, x1, #1, LSL 12
1 mov x1, #1
2 mov x2, #0x108a
3 sub x0, x1, x2, UXTB
4 sub x0, x1, x2, SXTB
UXTB 对 x2 寄存器的低 8 位数据进行无符号扩展,结果为 0x8a , 然后再计算 1-0x8a 的值,最终结果为 0xffffffffffffff77
SXTB 对 x2 寄存器的低 8 位数据进行有符号扩展,结果为 0xffffffffffffff8a,然后再计算 1-0xffffffffffffff8a ,最终结果为 0x77 。
sub x0, x1, x2, LSL, 3 //x0 = x1 0 (x2 << 3)
SUBS 指令
减法指令,但是会影响 PSTATE 寄存器的 N、Z、C、V 标志
该指令的计算过程: operand1 + NOT(operand2) + 1 // NOT 表示按位取反
mov x1, 0x3
mov x2, 0x1
subs x0, x1, x2
mrs x3, nzcv //读取 nzcv 寄存器的值——0x200000000
x2 的值为 0x1,按位取反后的值为 0xfffffffffffffffe , 3 + 0xfffffffffffffffe + 1 , 这个过程会发生无符号溢出,因此 4 个标志位中的 C=1,最终计算结果为 2 。
SBC 指令
进位减法指令,也就是最终计算结果需要考虑 PSTATE 寄存器的 C 标志位
该指令的计算过程;Xd = Xn + NOT(Xm) + C
mov x1, #3
mov x2, #1
sbc x0, x1, x2
mrs x3, nzcv
3 + not(1) + C , 1 按位取反为 0xfffffffffffffffe , 3 + 0xfffffffffffffffe 过程会发生溢出,所以 C=1,再嘉善标志位 C,结果为 2.
比较大小指令 CMP
CMP 指令用来比较两个数的大小,在 A64 中,CMP 指令内部调用 SUBS 指令来实现
cmp x1, x2 // x1 + not(x2) + 1
// 跳转到 label 处
b.cs label //CS 表示发生了无符号溢出,即 C 标志位置位,CC 表示 C 标志位没有置位
my_test:
mov x1, #3
mov x2, #2
1:
cmp x1, x2
b.cs lb
ret
b 指令的操作由后缀 cs 决定,cs 表示判断是否发生无符号溢出,3 + not(2) + 1 , not(2) = 0xfffffffffffffffd , 3 + 0xfffffffffffffffd + 1 = 1, ,这个过程发生了溢出,C 标志位置为 1, 所以 b.cs 的判断条件成立,跳转到标签 1 处,继续执行。
- 比较 x1 和 x2 的寄存器的值大小
my_test:
mov x1, #3
mov x2, #2
1:
cmp x1, x2
b.ls, 1b
ret
在比较 x1 和 x2 寄存器的值大小时,判断条件为 LS,表示无符号小于或者等于,那么,在这个比较过程中,我们就不需要判断 C 标志位了,直接判断 x1 寄存器的值是否 小于或等于 x2 寄存器的值即可。因此这里不会跳转到标签 1 处。
以条件标志位示例 array_index_mask_nospec
内核中 array_index_mask_nospec 函数用来实现一个掩码
- when i n d e x ≥ s i z e index \geq size index≥size, return 0
- when i n d e x < s i z e index < size index<size, return 掩码 0xFFFFFFFFFFFFFFFF
static inline unsigned long array_index_mask_nospec(unsigned long idx, unsigned long sz)
{
unsigned long mask;
asm volatile(
" cmp %1, %2\n"
" sbc %0, xzr, xzr\n"
: "=r" (mask)
: "r" (idx), "Ir" (sz)
: "cc");
csdb();
return mask;
}
上述内嵌汇编转成纯汇编代码
cmp x0, x1
sbc x0, xzr, xzr
x0 寄存器的值 idx,x1 寄存器的值 sz,当 l d x < s z ldx < sz ldx<sz 时,cmp 指令没有产生无符号数溢出,C 标志位为 0,当 i d x ≥ s z idx \geq sz idx≥sz cmp 指令产生了无符号溢出(其实内置是使用了 subs 指令来实现),C 标志位会置 1。
根据 SBC 指令的计算: 0 + NOT(0) + C = 0-1+C
-
当 index 小于 size 时,C = 0, 最终计算结果为 -1,也就是 0xFFFFFFFFFFFFFFFF 。
-
当 index 大于或等于 size 时,C = 1, 最终计算结果为 0.
移位指令
- LSL:逻辑左移指令,最高位会被丢弃,最低位补 0。
- LSR:逻辑右移指令,最高位补 0,最低位会被丢弃
- ASR:算术右移指令,最低位会被丢弃,最高位会按照符号进行扩展。
- AOR:循环右移指令,最低位会移动到最高位
A64 指令集里没有单独设置算术左移的指令,因为 LSL 指令会把最高位舍弃了
ldr w1, =0x8000008a
asr w2, w1, 1
lsr w3, w1, 1
- ASR 是算术右移指令,把 0x8000008A 右移一位并且对最高位进行有符号扩展,最后结果为 0xC0000045
- LSR 是逻辑右移指令,把 0x8000008A 右移一位并且在最高位补 0,最后结果为 0x40000045 。
位操作指令
与操作指令 AND
AND:按位 与 操作
ANDS:带条件标志位的 与 操作,影响 Z 标志位。
mov x1, #0x3
mov x2, #0
ands x3, x1, x2 // 0x3 和 0 做 “与” 操作
mrs x0, nzcv //与 的结果为 0,读取 NZCV 寄存器,可以看到 Z 标志位了
或操作指令 ORR
ORR Xd|SP, Xn, #imm
ORR Xd, Xn, shift #amount
- 立即数方式:对 Xn 寄存器的值与立即数 imm 进行 或 操作
- 寄存器方式:先对 Xm 寄存器的值做 移位 操作,然后再与 Xn 寄存器的值进行 或 操作
EOR Xd|SP, Xn, #imm
EOR Xd, Xn, Xm, shift #amount
- 立即数方式:对 Xn 寄存器的值与立即数 imm 进行 异或 操作
- 寄存器方式:先对 Xm 寄存器的值做 移位 操作,然后再与 Xn 寄存器的值进行 异或 操作
位段指令
位段插入 BFI
BFI Xd, Xn, #lsb, #width
BFI 指令的作用是用寄存器 Xn 寄存器中的 Bit[0, width-1] 替换 Xd 寄存器中的 Bit[lsb, lsb+width-1] 替换 Xd 寄存器中的 Bit[lsb, lsb + width-1] , Xd 寄存器中的其他位不变。
val &=~ (oxf << 4) //val 表示寄存器 A 的值
val |= ( (u64)0x5 << 4)
mov x0, #0 //寄存器 A 的值初始化为 0
mov x1, #0x5
bfi x0, x1, #4, #4 //往寄存器A的Bit[7,4}字段设置 0x5
BFI 指令把 X1 寄存器中的 Bit[3,0] 设置为 X0 寄存器中的 Bit[7,4], X0 寄存器中的 Bit[7,4] ,X0 寄存器的值是 0x50。
位段提取操作指令 UBFIX
UFBX Xd, Xn, #lsb, #width
UBFX 作用是提取 Xn 寄存器的 Bit[lsb, lsb+width-1], 然后存储到 Xd 寄存器中。另外 SBFX 和 UBFX 的区别只是SBFX 会进行符号扩展,例如 Bit[lsb, lsb+width-1] 为 1,那么写到 Xd 寄存器之后,所有的高位都必须为 1,。
mov x2, #0x8a
ubfx x0, x2, #4, #4
sbfx x1, x2, #4, #4
SBFX 指令在提取字段之后需要做符号扩展,当提取后的字段中最高位为 1 时,Xd 寄存器的最高位都要填充 1。当提取后的字段中最高位为 0 时,Xd 寄存器里最高位都要填充 0,最终,X1 寄存器的值为 0xFFFFFFFFFFFFFF8 。