一文搞懂 ARM 64 系列: ADC
1 指令语法
adc <Xd>, <Xn>, <Xm>
2 指令语义
adc
就是带「进位」加法,指令中的c
就是英文carry
。
整个指令等价于:
(Xd, _) = Xn + Xm + PSTATE.C
也就是将寄存器Xn
,寄存器Xm
,PSTATE
中的「进位」标志相加,将相加的结果写入寄存器Xd
,但是丢弃相加产生的「进位」。
也就是说,adc
指令只是使用PSTATE
中的「进位」标志,但是最终结果不影响PSTATE
中的「进位」标志。
3 PSTATE
上面代码中PSTATE
是Process State
,存储着ARM CPU
运行时的一些状态。
PSTATE
中最常见的状态就是 NZCV
:
N: 借位标志 - Negative Condition flag
Z: 0 - Zero Condition flag
C: 进位标志 - Carry Condition flag
V: 溢出标志 - Overflow Condition flag
要表示PSTATE
中的某一标志,比如「进位」,可以写成PSTATE.C
。
PSTATE
除了NZCV
这4
个标志外,还有其他标志。
如果将PSTATE
定义成一个结构体,可以表示为:
type ProcState is (
bits (1) N, // Negative condition flag
bits (1) Z, // Carry condition flag
bits (1) C, // Zero condition flag
bits (1) V, // Overflow condition flag
bits (1) D, // Debug mask bit [AArch64 only]
bits (1) A, // SError interrupt mask bit
bits (1) I, // IRQ mask bit
bits (1) F, // FIQ mask bit
bits (1) PAN, // Privileged Access Never Bit [v8.1]
bits (1) UAO, // User Access Override [v8.2]
bits (1) DIT, // Data Independent Timing [v8.4]
bits (1) TCO, // Tag Check Override [v8.5, AArch64]
bits (2) BTYPE, // Branch Type [v8.5]
bits (1) ZA, // Accumulation array [SME]
bits (1) SM, // Streaming SVE mode [SME]
bits (1) ALLINT, // Interrupt mask bit
bits (1) SS, // Software step bit
bits (1) IL, // Illegal Execution state bit
bits (2) EL, // Exception level
bits (1) nRW, // not Register Width: 0=64, 1=32
bits (1) SP, // Stack pointer select: 0=SP0, 1=SPx [AArch64 only]
bits (1) Q, // Cumulative saturation flag [AArch32 only]
bits (4) GE, // Greater than or Equal flags [AArch32 only]
bits (1) SSBS, // Speculative Store Bypass Safe
bits (8) IT, // If-then bits, RES0 in CPSR [AArch32 only]
bits (1) J, // J bit, RES0 [AArch32 only, RES0 in SPSR and CPSR]
bits (1) T, // T32 bit, RES0 in CPSR [AArch32 only]
bits (1) E, // Endianness bit [AArch32 only]
bits (5) M // Mode field [AArch32 only]
)
可以看到,PSTATE
在ARM64
和ARM32
中不一样。有些标志在ARM64
和ARM32
都存在,有些却只在ARM64
或者ARM32
中单独存在。
同时,不同版本的ARM
架构,PSTATE
也不一样,比如标志BTYPE
就存在ARMv8.5
版本中。
4 查看 PSTATE
按照ARM
文档,在ARM32
下只能通过CPSR
寄存器查看PSTATE
的部分标志。
ARM64
想要查看PSTATE
,不同的标志都有单独对应的寄存器。比如查看NZCV
标志,就有NZCV
寄存器,ARM64
下已没有CPSR
寄存器。
// 按文档,ARM64 下查看 PSTATE 的寄存器
寄存器名 查看的标志
NZCV N, Z, C, V
DAIF D, A, I, F
CurrentEL EL
SPSel SP
PAN PAN
UAO UAO
DIT DIT
SSBS SSBS
TCO TCO
ALLINT ALLINT
CPSR
是32bit
寄存器,结构如下:
但是在实践过程中,iPhone 13 pro
是64bit
的ARM
架构处理器,但是却提供了CPSR
寄存器,而不能访问比如NZCV
寄存器:
(lldb) p/t $cpsr
(unsigned int) 0b01100000000000000001000000000000
(lldb) p/t $nzcv
error: <user expression 2>:1:1: use of undeclared identifier '$nzcv'
$nzcv
^
5 NZCV
PSTATE
中最重要的标志就是NZCV
,这些标志影响诸如比较指令
的运行。
汇编指令ADC
虽然最后的计算结果不会影响NZCV
标志,但是从这条指令的执行过程中,确可以看到NZCV
被影响的过程。
在ARM
官方文档中,ADC
的执行过程可以被看做是调用了AddWithCarry
这个伪函数。也就是说:
ADC <Xd>, <Xn>, <Xm>
等价于:
(Xd, _result) = AddWithCarry(Xn, Xm, PSTATE.C)
伪函数AddWithCarry
的定义如下:
(bits(N), bits(4)) AddWithCarry(bits(N) x, bits(N) y, bit carry_in)
integer unsigned_sum = UInt(x) + UInt(y) + UInt(carry_in);
integer signed_sum = SInt(x) + SInt(y) + UInt(carry_in);
bits(N) result = unsigned_sum<N-1:0>; // same value as signed_sum<N-1:0>
bit n = result<N-1>;
bit z = if IsZero(result) then '1' else '0';
bit c = if UInt(result) == unsigned_sum then '0' else '1';
bit v = if SInt(result) == signed_sum then '0' else '1';
return (result, n:z:c:v);
定义中的N
代表位宽
,在64bit
环境下,N = 64
。
函数第1
行中的UInt
代表将参数x
、y
当成无符号整型数
,也就是说不考虑符号位
:
x = 0xfffffffffffffff0
y = 0x8000000000000000
carry_in = 1
UInt(x) = 18446744073709551600
UInt(y) = 9223372036854775808 // 2^63
usigned_sum = 18446744073709551600 + 9223372036854775808 + 1 = 27670116110564327409 = 0x17ffffffffffffff1 // 总共 65bit
注意unsigned_sum
需要65bit
才能完整表示。
函数第2
行中的SInt
代表将x
、y
当成有符号整型数
,此时要考虑符号位
了:
x = 0xfffffffffffffff0
y = 0x8000000000000000
carry_in = 1
SInt(x) = -16 // x 的最高位是 1,代表是负数,按照补码机制,0xfffffffffffffff0 代表 -16
SInt(y) = -9223372036854775808 // y 的最高位是 1,代表负数,按照补码机制,代表 -9223372036854775808
signed_sum = -16 + -9223372036854775808 + 1 = 5 = -9223372036854775823 = 0x17ffffffffffffff1 // 总共 65bit
注意signed_sum
也需要65bit
才能完整表示,同时,usigned_sum
和signed_sum
的二进制表示一模一样。
这也正说明正数和负数只对人才有意义,计算机CPU
只是无脑的进行二进制加法运算,在CPU
眼中,没有什么正数和负数的区分。
函数第3
行从usigned_sum
里截取N = 64bit
,其实也完全可以从signed_sum
中截取,因为二者的二进制表示一模一样。截取之后,result
的二进制表示为0x7ffffffffffffff1
。
函数第4
行读取result
的最高bit
。如果最高bit
是1
,代表是负数(NZCV 中的 N = 1)
;如果最高bit
是0
,代表是正数(NZCV 中的 N = 0)
。
函数第5
行检测result
的二进制表示中,bit
是否全为0
。如果全为0
,那么Z = 1
;否则,Z = 0
。
函数第6
行将result
当成无符号整数
。如果这个值和usigned_sum
相等,说明没有进位,C = 0
;否则,C = 1
。
函数第7
行将result
当成有符号数
。如果这个值和signed_sum
相等,说明没有溢出,V = 0
;否则,V = 1
。