/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,由此完成取绝对值的任务。

posted @ 2013-06-07 11:33  trirocky  阅读(4819)  评论(2编辑  收藏  举报