/src/pkg/math/abs.go源码阅读兼谈golang与汇编
开头的碎碎念:
对接微信公众平台的时候,开始有个字符串排序,我接触golang毕竟时间尚浅,很多东西都是能从网上找到就直接从网上找,结果就是找了好几个示例代码都不好用,好容易一个好用的,从头开始实现的,代码太多了。我就想,google应该把这些玩意都封装好了吧,不然一个新出的语言只有基础语法,没有强大的标准库,谁用这玩意啊。也就是那时候第一次接触src文件夹,后来发现pkg里的那些go文件是绝好的学习资料。
那么多文件、文件夹从哪开始看呢,我的原则,先找没有依赖性的,也就是没有import的,这么寻摸着就找到了math文件夹。笨方法,从a开始按顺序来呗,这不就碰到了abs.go
难以理解的第12行:
// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package math // Abs returns the absolute value of x. // // Special cases are: // Abs(±Inf) = +Inf // Abs(NaN) = NaN func Abs(x float64) float64 func abs(x float64) float64 { switch { case x < 0: return -x case x == 0: return 0 // return correctly abs(-0) } return x }
孤零零的一行,一个方法声明,别的啥都没有,完全不解其意。
下面那个abs()不用我说了吧,很简单的,取绝对值。
不管了,先到math文件夹看看,abs_386.s、abs_amd64.s、abs_arm.s有这三个文件估计跟那行不知道哪来的代码有关系,.s结尾的,汇编语言文件,继续发动google的威力,golang、汇编混编,如此便找到了http://www.mikespook.com/2013/02/%E7%BF%BB%E8%AF%91go-%E5%92%8C%E6%B1%87%E7%BC%96/
让程序跑起来:
先让这段程序跑起来吧,因为我的机器是64位的,所以我把abs.go、abs_amd64.s两个文件拷贝到别处,abs.go的包改成了mymath,另外写了一个简单的测试程序
package main import( "fmt" "mymath" ) func main(){ fmt.Println(mymath.Abs(-12.00)) }
在/pkg/tool/windows_amd64下有很多有用的工具,6g、6l啥的,不过常用的都被go命令给封装了,直接go build、go install等命令就解决了。
涉及到汇编的,主要是6a,上面的代码按如下顺序编译:
6a abs_amd64.s(生成abs_amd64.6)
6g –o _go_.6 abs.go(生成_go_.6)
pack grc abs.a _go_.6 abs_amd64.6(生成abs.a)
本来是想直接让主程序调用目录下的库的,import “./mymath”,不过,windows下提示出错import path contains invalid characte ‘:’:”c:/xxx/xxx”,所以只得将abs.a扔到pkg/windows_amd64文件夹中。
剩下的就是:
6g test.go(生成test.6)
6l test.6(生成6.out.exe)
从简单的golang编译器自动生成的汇编入手:
先看一个新的命令,golang编译器自动生成汇编中间代码的命令,go tool 6g –S XXX.go,其实上面的那些命令也都可以用go tool XXX来代替,go命令把那些命令都封装进去了。
来个最简单的代码吧:
package asm func Asm(i int)int{ return i }
go tool 6g –S asm.go:
--- prog list "Asm" --- 0000 (asm.go:3) TEXT Asm+0(SB),$0-16 0001 (asm.go:3) LOCALS ,$0 0002 (asm.go:3) TYPE i+0(FP){int},$8 0003 (asm.go:3) TYPE ~anon1+8(FP){int},$8 0004 (asm.go:4) MOVQ i+0(FP),BX 0005 (asm.go:4) MOVQ BX,~anon1+8(FP) 0006 (asm.go:4) RET ,
plan9汇编,语法跟AT&T颇为类似,传值是前面是源,后面是目的,这点跟masm、nasm啥的都是反的。
000行:TEXT相当于定义了一个函数,Asm函数名,+0(SB)golang生成的都有这玩意;$0-16,经过我的反复尝试,起码对于int、float64这两者而言,是(参数个数+返回值个数)*8(这都是我自己验证的,没啥科学依据,相关文档我也翻阅过一些,不过鸟语不过关,将把能看懂的东西里没有我需要的,大胆假设,小心论证现在还做不到)。
001行:我估计是执行指令的位置,不过这都不重要,关键是后头的。
002、003行:i是变量名,~anon1其实也是变量名(系统自动生成的)
稍微修改下
func Asm(i int) (j int){ j=i return }
则003行,就变成了j+8(FP)
至于0(FP)、8(FP),对于int来说,每个数字占8个字节(64位下),所以传入的参数,第一个就是+0(FP),第二个+8(FP),第三个+16(FP),第四个+24(FP)…
返回值,如果有多个返回值,第一个+(8+最后一个传入参数的数值)(FP),后面都是依次+8
{int}标明了数据类型,$8表明占据8个字节
004行:将参数值传给寄存器BX,MOVQ,传递四字
005行:将BX中的值传给返回值
006行:RET
看看float64又是啥样的:
package asm func Asm(f float64)float64{ return f }
--- prog list "Asm" --- 0000 (asm.go:3) TEXT Asm+0(SB),$0-16 0001 (asm.go:3) LOCALS ,$0 0002 (asm.go:3) TYPE i+0(FP){int},$8 0003 (asm.go:3) TYPE ~anon1+8(FP){float64},$8 0004 (asm.go:4) MOVQ i+0(FP),X0 0005 (asm.go:4) MOVQ X0,~anon1+8(FP) 0006 (asm.go:4) RET ,
可以看出与前面用int去尝试大致相同,只是BX寄存器变成了X0,可以推测X0就是浮点数寄存器,有X0,大胆推测会有X1、X2、X3…
试试吧
package asm func Asm(f1,f2 float64) float64{ return f1+f2 }
--- prog list "Asm" --- 0000 (asm.go:3) TEXT Asm+0(SB),$0-24 0001 (asm.go:3) LOCALS ,$0 0002 (asm.go:3) TYPE f1+0(FP){float64},$8 0003 (asm.go:3) TYPE f2+8(FP){float64},$8 0004 (asm.go:3) TYPE ~anon2+16(FP){float64},$8 0005 (asm.go:4) MOVSD f1+0(FP),X0 0006 (asm.go:4) MOVSD f2+8(FP),X1 0007 (asm.go:4) ADDSD X1,X0 0008 (asm.go:4) MOVSD X0,~anon2+16(FP) 0009 (asm.go:4) RET ,
abs_amd64.s:
// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // func Abs(x float64) float64 TEXT ·Abs(SB),7,$0 MOVQ $(1<<63), BX MOVQ BX, X0 // movsd $(-0.0), x0 MOVSD x+0(FP), X1 ANDNPD X1, X0 MOVSD X0, ret+8(FP) RET
折腾一番终于到这了。
第一行就当固定格式吧,函数名替换下就好。
MOVQ $(1<<63), BX MOVQ BX, X0
1右移动63位,传给X0,此时X0二进制表示是1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
MOVSD x+0(FP), X1
MOVSD移动标量双精度浮点值,将参数x的值传给X1
ANDNPD X1, X0
ANDNPD压缩双精度浮点值逻辑位与非,将目标操作数的取反,再与源操作数执行逻辑位“与”操作,结果存储到目标操作数
即对X0取反,再与X1相与,最后结果存储到X0中
以上操作所完成的也就是将符号位置0,由此完成取绝对值的任务。