为啥写
Go的汇编一直是我感兴趣的地方,为了验证之前所学的汇编知识和好玩 ,我决定往Go官方提交一个性能patch。 所以到官方的标准库里搜了一圈,发现adler32并没有硬件加速的实现,而Intel已经公布了相关的SSE加速实现 https://github.com/01org/isa-l/
所以我决定把Intel的抄过来,结果不停地掉坑和爬出来,终于提交了patch(撒花)
https://go-review.googlesource.com/c/51850
希望在Go 1.10发布的时候能进入官方源:)
坑
- 写法的区别
- Go汇编不支持
- 难以调试
- 内存越界与LEA
- 向官方提交代码需要注意的地方,保证Change ID一致
坑一:Intel和AT&T的写法的区别
Intel写法是: opcode source destination
AT&T 也就是Go官方汇编语言的写法正好是反过来的: opcode destination source
抄代码的时候有点绕
坑二 Go汇编不支持:
Go的维护者们对于新添加汇编opcode一直是很保守的,比如2001年前后就有的SSE2 里的PSHLLW()竟然不支持(= =||)。 所以得自己填BYTE,比如官方文档中的MOVQ用BYTE方式编写 https://golang.org/doc/asm
BYTE $0x0f; BYTE $0x6f; BYTE $0x00 // MOVQ (AX), M0
但这里就有一个坑,比如PSHLLD和PSHRQ的Opcode是一样的……只是按/r 寄存器类型进行区别。需要注意
坑三:调试困难
一般代码测试时,都可以直接输出日志,帮助定位问题,但是汇编不行,所以我是通过把需要的值放入某个不用的寄存器, 例如
#define debug R15
// min(a, b int) int
TEXT min(SB), NOSPLIT, $0
在需要的时候,提前把函数返回
MOVQ debug, ret+16(FP)
RET
当然应该有更好的方法,可以把所有寄存器打印出来,不过这个方法够我自己调试用了。
坑四:内存越界与LEA
一般程序中都是指针,或者直接结构体。不过,汇编这里回归本真,只有内存地址和寄存器。所以一定要小心访问数据的边界和跳转的内存地址。 这里学到了一个LEA的用法,地址计算器,把内存地址到目标寄存器里。
LEAQ 0(data)(size*1), end
具体用法
- 0: 写死的偏移量
- data: 偏移值
- size: 动态偏移量,可以用乘法
坑五:
提交代码到gerrit要保持Change ID相同,要不然算一个新的Change。
附录:opcode坑
- MOVOU: O=oct, U=unaligned, 指的是八个word,即128位,用于XXM寄存器的移动。
- TESTQ: 对比值,并影响FLAG
- DIVL:低位除,如果高位有数据就会出错
资料: Go官方介绍: https://golang.org/doc/asm