本文档是 Go编译器 GC
使用的非通用形式的汇编语言快速大纲。文档并不完整。
汇编器基于 Plan 9 汇编器的输入风格。详细文档在这里。如果你准备写一些汇编语言,那么虽然此文档是基于 Plan 9 的,你也应该通读。本文提供了语法摘要和与其解释内容的区别,并且描述了编写汇编与 Go 交互时所适用的特性。
最重要的是,Go 的汇编器并不是底层的直接表示。有一些是直接的映射,有一些不是。这是因为编译套件在常规流程中并不需要汇编器。相反,编译器针对一种半抽象的指令集操作,而且指令选择发生在一部分发生在代码生成之后。汇编器工作在半抽象状态下,所以当你看到类似指令 MOV
,工具链实际生成的操作也许并不是移动,而是清除指令或者加载指令。或者也可能与实际指令完全对应。一般来说,特定机器的操作倾向于它们自身,而更通用的概念(如内存移动、子程序调用、返回等)则更加抽象。细节根据体系架构会有不同,我们非常抱歉关于这种不精确,情况还没有完全定义。
汇编器程序是一种解析半抽象指令集的描述并将其转换为输入给链接器指令的方法。如果想要看到给定体系的汇编指令(如:amd64),在标准库源码中有很多实例(比如 runtime
math/big
)。你可以测试编译器生成的汇编代码(实际输出可能和这里有出入):
$ cat x.go
package main
func main() {
println(3)
}
$ GOOS=linux GOARCH=amd64 go tool compile -S x.go # or: go build -gcflags -S x.go
"".main STEXT size=74 args=0x0 locals=0x10
0x0000 00000 (x.go:3) TEXT "".main(SB), $16-0
0x0000 00000 (x.go:3) MOVQ (TLS), CX
0x0009 00009 (x.go:3) CMPQ SP, 16(CX)
0x000d 00013 (x.go:3) JLS 67
0x000f 00015 (x.go:3) SUBQ $16, SP
0x0013 00019 (x.go:3) MOVQ BP, 8(SP)
0x0018 00024 (x.go:3) LEAQ 8(SP), BP
0x001d 00029 (x.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (x.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (x.go:3) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (x.go:4) PCDATA $0, $0
0x001d 00029 (x.go:4) PCDATA $1, $0
0x001d 00029 (x.go:4) CALL runtime.printlock(SB)
0x0022 00034 (x.go:4) MOVQ $3, (SP)
0x002a 00042 (x.go:4) CALL runtime.printint(SB)
0x002f 00047 (x.go:4) CALL runtime.printnl(SB)
0x0034 00052 (x.go:4) CALL runtime.printunlock(SB)
0x0039 00057 (x.go:5) MOVQ 8(SP), BP
0x003e 00062 (x.go:5) ADDQ $16, SP
0x0042 00066 (x.go:5) RET
0x0043 00067 (x.go:5) NOP
0x0043 00067 (x.go:3) PCDATA $1, $-1
0x0043 00067 (x.go:3) PCDATA $0, $-1
0x0043 00067 (x.go:3) CALL runtime.morestack_noctxt(SB)
0x0048 00072 (x.go:3) JMP 0
...
复制代码
FUNCDATA
和 PCDATA
包含了垃圾回收使用的信息,会在编译器中介绍。
想要查看链接后放入二进制文件中的内容,使用 go tool objdump
来查看:
$ go build -o x.exe x.go
$ go tool objdump -s main.main x.exe
TEXT main.main(SB) /tmp/x.go
x.go:3 0x10501c0 65488b0c2530000000 MOVQ GS:0x30, CX
x.go:3 0x10501c9 483b6110 CMPQ 0x10(CX), SP
x.go:3 0x10501cd 7634 JBE 0x1050203
x.go:3 0x10501cf 4883ec10 SUBQ $0x10, SP
x.go:3 0x10501d3 48896c2408 MOVQ BP, 0x8(SP)
x.go:3 0x10501d8 488d6c2408 LEAQ 0x8(SP), BP
x.go:4 0x10501dd e86e45fdff CALL runtime.printlock(SB)
x.go:4 0x10501e2 48c7042403000000 MOVQ $0x3, 0(SP)
x.go:4 0x10501ea e8e14cfdff CALL runtime.printint(SB)
x.go:4 0x10501ef e8ec47fdff CALL runtime.printnl(SB)
x.go:4 0x10501f4 e8d745fdff CALL runtime.printunlock(SB)
x.go:5 0x10501f9 488b6c2408 MOVQ 0x8(SP), BP
x.go:5 0x10501fe 4883c410 ADDQ $0x10, SP
x.go:5 0x1050202 c3 RET
x.go:3 0x1050203 e83882ffff CALL runtime.morestack_noctxt(SB)
x.go:3 0x1050208 ebb6 JMP main.main(SB)
复制代码
常量
尽管汇编器遵循 Plan 9 的规范,但这是一个独立程序,会有一些不同。其中一个就是常量评估。汇编器中的常量表达式是按照 Go 的操作符优先级来解析的,而不是类 C 的优先级。所以, 3&1<<2
是 4 而不是 0。它按照 (3&1)<<2
来解析,而不是 3&(1<<2)
。而且,常量会被解析为 64位无符号整形。所以 -2
不是整形数减2,而是具有同样位信息的64位无符号整形。这个区别基本没什么影响,除了要注意避免在对右操作数高位置位的情况下进行歧义操作、除法、右移。
符号
某些符号,例如 R1
或者 LR
,已经预定义,并且指向了寄存