arm-v8a 函数调用传参流程
传递
我们先来看汇编?(看汇编去这个网站挺不错的:https://godbolt.org/),
a(int, long long): // @a(int, long long)
sub sp, sp, #32 // 为局部变量分配32字节的栈空间
str w0, [sp, #28] // 将第一个参数 w0 (int) 存储到栈上偏移28字节的位置
str x1, [sp, #16] // 将第二个参数 x1 (long long) 存储到栈上偏移16字节的位置
ldr w8, [sp, #28] // 从栈上偏移28字节的位置加载 w0 到 w8
add w8, w8, #1 // w8 = w8 + 1,即 w8 加1
str w8, [sp, #12] // 将 w8 存储到栈上偏移12字节的位置
ldr x8, [sp, #16] // 从栈上偏移16字节的位置加载 x1 到 x8
add x8, x8, #1 // x8 = x8 + 1,即 x8 加1
str x8, [sp] // 将 x8 存储到栈上偏移0字节的位置
ldrsw x8, [sp, #12] // 从栈上偏移12字节的位置加载 w8(带符号扩展)到 x8
ldr x9, [sp] // 从栈上偏移0字节的位置加载 x8 到 x9
add x8, x8, x9 // x8 = x8 + x9,即 w8 + (x1 + 1)
mov w0, w8 // 将 w8 复制到 w0
add sp, sp, #32 // 释放32字节的栈空间
ret // 返回
main: // @main
sub sp, sp, #32 // 为局部变量和调用保存栈帧分配32字节的栈空间
stp x29, x30, [sp, #16] // 将帧指针和返回地址存储到栈上偏移16字节的位置
add x29, sp, #16 // 设置帧指针 x29 指向当前栈帧的偏移16字节的位置
mov w0, #1 // 将整数 1 赋值给 w0
mov x1, #2 // 将整数 2 赋值给 x1
bl a(int, long long) // 调用函数 `a(int, long long)`
stur w0, [x29, #-4] // 将函数 `a` 的返回值存储到栈上偏移 x29 - 4 字节的位置
mov w0, wzr // 将 w0 清零
ldp x29, x30, [sp, #16] // 从栈上恢复帧指针和返回地址
add sp, sp, #32 // 释放32字节的栈空间
ret // 返回sp
默认情况下,ARM64使用下降栈(Descending Stack),即栈顶在低地址方向增长。在这种情况下,SP寄存器指向栈顶元素的地址,压栈操作会将SP减小,而弹栈操作会将SP增大。说简单一点,不喜欢那种奇奇怪怪的概念,其实sp就是一个提前分配了一个指定大小的空间的尾指针,然后反着用罢了,至于为什么这样?反这样用可以让您的代码在溢出的时候快速的触发保护机制,终止进程。【1】【2】会告诉你为什么...
w0 or x0
有人问,那就补充一下吧,在 ARM64 中,w 寄存器是 x 寄存器的低 32 位部分。例如,w0 是 x0 的低 32 位部分。因此,当传递 32 位整数时,可以使用 w 寄存器,这实际上只是操作 x 寄存器的低 32 位。
X registers
ARM64位参数调用规则遵循AAPCS64,规定堆栈为满递减堆栈。
寄存器调用规则如下:
X0~X7:用于传递子程序参数和结果,使用时不需要保存,多余参数采用堆栈传递,64位返回结果采用X0表示,128位返回结果采用X1:X0表示。
X8:用于保存子程序返回地址, 尽量不要使用 。
X9~X15:临时寄存器,使用时不需要保存。
X16~X17:子程序内部调用寄存器,使用时不需要保存,尽量不要使用。
X18:平台寄存器,它的使用与平台相关,尽量不要使用。
X19~X28:临时寄存器,使用时必须保存。
X29:帧指针寄存器,用于连接栈帧,使用时需要保存。
X30:链接寄存器LR
X31:堆栈指针寄存器SP或零寄存器ZXR
浮点数的传递
64位下NEON寄存器:
32个B寄存器(B0~B31),8bit
32个H寄存器(H0~H31),半字 16bit
32个S寄存器(S0~S31),单子 32bit
32个D寄存器(D0~D31),双字 64bit
32个Q寄存器(V0~V31),四字 128bit

v0和q0其实是同一个寄存器。