Go汇编迁移
一般情况下,在鲲鹏处理器上搭建好编译系统就能直接编译运行代码,但对于存在Go汇编的情况,即*.s汇编文件,需额外进行迁移。
现象描述:
源码包含x86 Go汇编时,编译报错:missing function body。
Go中寄存器包括通用寄存器和伪寄存器,以下重点介绍伪寄存器。
- 伪寄存器
x86平台下Go存在四种伪寄存器FP、PC、SB、SP,实际是对内存位置的一个引用。
- FP(frame pointer):帧指针,保存参数和本地变量,用来标识函数参数、返回值
- SP(stack pointer):栈指针,存放栈的偏移地址,它指向本地栈帧的顶部
- SB(static base):静态基指针,用来表示全局的变量或者函数
- PC(Program counter):程序指针,即处理器中常见的PC寄存器,存储需执行指令地址
- 汇编代码迁移
Go编译器会输出一种抽象的汇编代码,这种汇编并不对应某种真实的硬件架构。Go汇编器使用这种伪汇编,为目标硬件生成具体的二进制文件。伪汇编的优点在于更易将Go移植到新的架构上。但即使是伪汇编,x86平台与鲲鹏平台还是存在一些差异,下面列举几点常见的差异:
- 数据搬运的长度由MOV的后缀决定,同一长度的搬运在不同平台指令有差异,表1列出了常见长度在x86与鲲鹏上指令的差异。
- 鲲鹏平台MOVD.W+栈地址偏移,代表前增量,表示栈扩容,对应指令SUBQ +栈地址偏移;同理,MOVD.P+栈地址偏移,代表后增量,表示缩栈,对应x86 ADDQ +栈地址偏移;MOVD.W与MOVD.P在汇编代码中成对出现。
SUBQ $24, SP //栈顶指针下移24字节 …… //中间省略一系列具体功能汇编指令 ADDQ $24, SP //栈顶指针上移24字节
鲲鹏平台下示例:
MOVD.W R30, -24(RSP) //栈顶指针下移24字节 …… //中间省略一系列具体功能汇编指令 MOVD.P 24(RSP), R30 //栈顶指针上移24字节
- 常用计算指令:
指令功能
x86
鲲鹏
加法
ADDQ
ADD
减法
SUBQ
SUB
乘法
IMULQ
MUL
比较
CMPQ
CMP
更多指令替换可参考如下链接:
https://www.slideshare.net/linaroorg/optimizing-golang-for-high-performance-with-arm64-assembly-sfo17314
示例:
以下示例通过Go汇编实现两个整数相加,对比不同平台下汇编实现的差异。
源代码函数:
func Add(a, b int64) int64 { return a+b }
- x86平台Go汇编实现:
创建一个add.go文件,此文件中只声明函数,然后在add.go的同名目录下创建add_amd64.s文件,实现如下:
TEXT ·Add+0(SB), $0-24 MOVQ a+0(FP), BX // 将参数a的值复制到BX寄存器中 MOVQ b+8(FP), BP // 将参数b的值复制到BP寄存器中 ADDQ BP, BX // 将BP寄存器与BX寄存器值相加,存入BX寄存器中 MOVQ BX, ret+16(FP) // 把BX寄存器的值复制到FP+16的位置, 即返回值的内存位置里 RET // 调用RET返回
TEXT标识表示开头,·Add为{package}·{function}格式,如果函数属于当前package,{package}可省略,注意Add前unicode中间点"·",这是为了包名分隔Add这个函数,最后的数字$0-24,其中0表示函数栈帧大小为0,24表示参数及返回值的大小;参数是2个int64变量,返回值是1个int64变量,共24字节。
- 鲲鹏平台Go汇编实现:
创建一个add.go文件,此文件中只声明函数,然后在add.go的同名目录下创建add_arm64.s,实现如下:
TEXT ·Add+0(SB), $0-24 // 解释和上文x86类似 MOVD a+0(FP), R0 MOVD b+8(FP), R1 ADD R1, R0, R0 MOVD R0, ret+16(FP) RET
通过对比发现,即使简单的源代码在Go汇编里实现也存在较大差异,如MOV指令、ADD指令和寄存器命名等。
一般情况下,包含Go汇编的文件指令多且复杂,不同架构差异较大,对Go原理掌握有较高要求,如时间紧急、汇编转换难以实现时,在了解汇编代码实现逻辑前提下,可编写Go代码(非汇编)实现代码功能。