MacOS Arm64 汇编 part 5 - MacOS Arm64 Assembly part 5

  • Multiply, Divide, and Accumulate
  • Floating-Point Operations
  • Neon Coprocessor

Multiplication

1
MUL Xd, Xn, Xm

其中 Xd = Xn * Xm

  • Xd 是乘法结果的 lower 64 bits 对应于 X 版本,而 W 版本则为保留 lower 32 bits
  • mul 无后缀 s 版本,无法检测溢出
  • 所有操作数均为寄存器,无立即数
  • 不存在独立的有无符号版本的乘法,乘法无补码

为了克服这些限制,因此有下面四个版本

1
2
3
4
SMULH   Xd, Xn, Xm
SMULL Xd, Wn, Wm
UMULH Xd, Xn, Xm
UMULL Xd, Wn, Wm
  • SMULLUMULL 允许我们计算两个 32 bits 数的 64 bits 乘积
  • SMULLSMULH 用于带符号数
  • UMULLUMULH 用于无符号数

SMULHUMULH 给我们 higher 64 bits 的乘法结果,SMULHMUL 连用即可获得完整的 128 bits 的带符号数乘法结果,而 UMULHMUL 连用即可获得完整的 128 bits 的无符号数乘法结果

下面几个指令计算乘法的相反数

1
2
3
MNEG    Xd, Xn, Xm
SMNEGL Xd, Wn, Wm
UMNEGL Xd, Wn, Wm

Division

整数除法

1
2
SDIV  Xd, Xn, Xm
UDIV Xd, Xn, Xm
  • Xd = Xn / Xm,其中 Xn 是被除数,Xm 是除数
  • 所有寄存器可以都可以是 XW
  • s 后缀版本
  • 不能除 ,这些指令均会返回 ,这是容易误导的
  • 无法简单计算 128 bits 除法
  • 这一指令只返回商,无余数

Multiply and Accumulate

对于点积或矩阵乘法,往往需要做先乘后累加的运算

Accumulate Instructions

1
2
3
4
5
6
MADD      Xd, Xn, Xm, Xa
MSUB Xd, Xn, Xm, Xa
SMADDL Xd, Wn, Wm, Xa
UMADDL Xd, Wn, Wm, Xa
SMSUBL Xd, Wn, Wm, Xa
UMSUBL Xd, Wn, Wm, Xa

这些是带累积的乘法指令,事实上大多数乘法指令都是这些指令的别名,通过给 Xa 零寄存器

  • Xd = Xa + Xn * XmXd = Xa - Xn * Xm 对应 ADD 和 SUB

Floating-Point Operations

Defining Floating-Point Numbers

使用 .single.double 定义单精度或双精度浮点数

对于 16 bits 的半精度浮点数,无法直接定义,需要转换

About FPU Registers

ARM 的 FPU 和 NEON 协处理器共享一组寄存器,其中 32 个 128-bit 寄存器命名为 V0-V31,而起有 D,S,H 三个版本,分别对应 64-bit, 32-bit, 16-bit

FPU 只能处理最大 64 bits 的数据,对于 128 bits 需要 NEON 处理器,它将 128-bit 寄存器命名为 Q0-Q31

  • V8-V15 由被调用者负责存储与恢复
  • V0-V7 作为参数传递用途,包括剩余寄存器,需要调用者保存

对于这些寄存器仍可以用 stp, str, ldp, ldr,需要注意的是 Q 寄存器需要用 stpldp

Loading and Saving FPU Registers

可以使用 FMOV 在 CPU 整数寄存器以及 FPU 的浮点寄存器之间移动数据,同时也可用于移动 FPU 中的寄存器

通常只能移动相同大小的寄存器,但是小寄存器可以移到大寄存器
需要注意的是 FMOV 不会进行任何转换

Performing Basic Arithmetic

对于下面的每一条指令,都能使用 3 种寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
FADD    Hd, Hn, Hm    // Hd = Hn + Hm
FADD Sd, Sn, Sm // Sd = Sn + Sm
FADD Dd, Dn, Dm // Dd = Dn + Dm
FSUB Dd, Dn, Dm // Dd = Dn - Dm
FMUL Dd, Dn, Dm // Dd = Dn * Dm
FDIV Dd, Dn, Dm // Dd = Dn / Dm
FMADD Dd, Dn, Dm, Da // Dd = Da + Dm * Dn
FMSUB Dd, Dn, Dm, Da // Dd = Da – Dm *Dn
FNEG Dd, Dn // Dd = - Dn
FABS Dd, Dn // Dd = Absolute Value( Dn )
FMAX Dd, Dn, Dm // Dd = Max( Dn, Dm )
FMIN Dd, Dn, Dm // Dd = Min( Dn, Dm )
FSQRT Dd, Dn // Dd = Square Root( Dn )

存在带 S 后缀的版本

Performing Floating-Point Conversions

将低精度转为高精度,以及整数,或从整数转为浮点,同时有多种 rounding 方法可选

1
2
3
4
FCVT    Dd, Sm
FCVT Sd, Dm
FCVT Sd, Hm
FCVT Hd, Sm

这些是浮点间精度互转

1
2
SCVTF    Dd, Xm        // Dd = signed integer from Xm
UCVTF Sd, Wm // Sd = unsigned integer from Wm

此外还有更多的整数到浮点

1
2
3
4
5
6
7
8
FCVTAS    Wd, Hn    // signed, round to nearest
FCVTAU Wd, Sn // unsigned, round to nearest
FCVTMS Xd, Dn // signed, round towards minus infinity
FCVTMU Xd, Dn // unsigned, round towards minus infinity
FCVTPS Xd, Dn // signed, round towards positive infinity
FCVTPU Xd, Dn // unsigned, round towards positive infinity
FCVTZS Xd, Dn // signed, round towards zero
FCVTZU Xd, Dn // unsigned, round towards zero

Comparing Floating-Point Numbers

通过比较指令设置条件标志,只允许 #0.0 这一个立即数,因为不存在零浮点寄存器

1
2
3
4
5
6
FCMP    Hd, Hm
FCMP Hd, #0.0
FCMP Sd, Sm
FCMP Sd, #0.0
FCMP Dd, Dm
FCMP Dd, #0.0

因为浮点数的等于比较会有问题,因此往往采用比较

NEON Coprocessor

可以处理 64 bits 以及 128 bits 寄存器,在并行化时实际上将这些寄存器分成等宽的几块,分别一次性的计算

1
2
ADD Vd.T, Vn.T, Vm.T    // Integer addition
FADD Vd.T, Vn.T, Vm.T // floating-point addition

其中 T 必须是

  • For ADD: 8B, 16B, 4H, 8H, 2S, 4S or 2D
  • For FADD: 4H, 8H, 2S, 4S or 2D

Calculating 4D Vector Distance

distance.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//
// Example function to calculate the distance
// between 4D two points in single precision
// floating point using the NEON Processor
//
// Inputs:
// X0 - pointer to the 8 FP numbers
// they are (x1, x2, x3, x4),
// (y1, y2, y3, y4)
// Outputs:
// W0 - the length (as single precision FP)

.global distance // Allow function to be called by others
.align 4

//
distance:
// load all 4 numbers at once
LDP Q2, Q3, [X0]

// calc V1 = V2 - V3
FSUB V1.4S, V2.4S, V3.4S
// calc V1 = V1 * V1 = (xi-yi)^2
FMUL V1.4S, V1.4S, V1.4S
// calc S0 = S0 + S1 + S2 + S3
FADDP V0.4S, V1.4S, V1.4S
FADDP V0.4S, V0.4S, V0.4S
// calc sqrt(S0)
FSQRT S4, S0
// move result to W0 to be returned
FMOV W0, S4

RET

其中 FADDP 按照 V0[0]=V1[0]+V1[1] 以及 V0[1]=V1[2]+V1[3] 的成对相加方式,保留前两通道的累加结果,并忽略最后两通道
两次 FADDP 即实现了 4 项累加

main.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//
// Main program to test our distance function
//
// W19 - loop counter
// X20 - address to current set of points

.global main // Provide program starting address to linker
.align 4

//

.equ N, 3 // Number of points.

main:
STP X19, X20, [SP, #-16]!
STR LR, [SP, #-16]!

ADRP X20, points@PAGE // pointer to current points
ADD X20, X20, points@PAGEOFF

MOV W19, #N // number of loop iterations

loop: MOV X0, X20 // move pointer to parameter 1 (r0)

BL distance // call distance function

// need to take the single precision return value
// and convert it to a double, because the C printf
// function can only print doubles.
FMOV S2, W0 // move back to fpu for conversion
FCVT D0, S2 // convert single to double
FMOV X1, D0 // return double to r2, r3
STR X1, [SP, #-16]!
ADRP X0, prtstr@PAGE // load print string
ADD X0, X0, prtstr@PAGEOFF
BL _printf // print the distance
ADD SP, SP, #16

ADD X20, X20, #(8*4) // 8 elements each 4 bytes
SUBS W19, W19, #1 // decrement loop counter
B.NE loop // loop if more points

MOV X0, #0 // return code
LDR LR, [SP], #16
LDP X19, X20, [SP], #16
RET

.data
points: .single 0.0, 0.0, 0.0, 0.0, 17.0, 4.0, 2.0, 1.0
.single 1.3, 5.4, 3.1, -1.5, -2.4, 0.323, 3.4, -0.232
.single 1.323e10, -1.2e-4, 34.55, 5454.234, 10.9, -3.6, 4.2, 1.3
prtstr: .asciz "Distance = %f\n"