鲲鹏社区首页
中文
注册
我要评分
文档获取效率
文档正确性
内容完整性
文档易理解
在线提单
论坛求助

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与鲲鹏上指令的差异。
      表1 数据搬运的长度在x86与鲲鹏上的指令差异

      操作数长度

      x86

      鲲鹏

      8 bytes

      MOVQ

      MOVD

      4 bytes

      MOVL

      MOVW

      2 bytes

      MOVW

      MOVH

      1 byte

      MOVB

      MOVB

    • 鲲鹏平台MOVD.W+栈地址偏移,代表前增量,表示栈扩容,对应指令SUBQ +栈地址偏移;同理,MOVD.P+栈地址偏移,代表后增量,表示缩栈,对应x86 ADDQ +栈地址偏移;MOVD.W与MOVD.P在汇编代码中成对出现。

      x86平台下示例:

      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代码(非汇编)实现代码功能。