![Go语言底层原理剖析](https://wfqqreader-1252317822.image.myqcloud.com/cover/131/40795131/b_40795131.jpg)
1.13 机器码生成——汇编器
在SSA阶段,编译器先执行与特定指令集无关的优化,再执行与特定指令集有关的优化,并最终生成与特定指令集有关的指令和寄存器分配方式。在ssa/gen/genericOps.go中,包含了和特定指令集无关的Op操作。在ssa/gen/AMD64Ops.go中,包含了和AMD64指令集相关的操作。
在SSA lower阶段之后,就开始执行与特定指令集有关的重写与优化,在genssa阶段,编译器会生成与单个指令对应的obj/link.go中的Prog结构。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_35_3.jpg?sign=1738868413-FODekxxJ6rHcoDIPOeHkwXnZE84AzNhu-0-d5ef382be0b05929876f68921f40fe8d)
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_36_1.jpg?sign=1738868413-O70db5zIj79Gtv7AXObvLyWcOWRllo87-0-0a1ec708aa966754f8c3c0efc7a6d27f)
例如,最终生成的指令MOVL R1,R2会被Prog表示为As=MOVL,From=R1,To=R2。Pcond代表跳转指令,除此之外,还有一些与特定指令集相关的结构。
在SSA后,编译器将调用与特定指令集有关的汇编器(Assembler)生成obj文件,obj文件作为链接器(Linker)的输入,生成二进制可执行文件。internal/obj目录中包含了汇编与链接的核心逻辑,内部有许多与机器码生成相关的包。不同类型的指令集(amd64、arm64、mips64等)需要使用不同的包生成。Go语言目前能在所有常见的CPU指令集类型上编译运行。
汇编和链接是编译器后端与特定指令集有关的阶段。由于历史原因,Go语言的汇编器基于了不太常见的plan9汇编器的输入形式[5]。需要注意的是,输入汇编器中的汇编指令不是机器码的表现形式,其仍然是人类可读的底层抽象。在Go语言runtime及math/big标准库中,可以看到许多特定指令集的汇编代码,Go语言也提供了一些方式用于查看编译器生成的汇编代码。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_36_2.jpg?sign=1738868413-6M05w4y7tJzV4pRjEH7Dqe8TG8LLCnKw-0-e8ae85b086b9c4f35655c1fb683f2937)
对于上面的简单程序,其输出的汇编代码如下所示(笔者删除了FUNCDATA与PCDATA这两个与垃圾回收有关的操作),这段汇编代码显示了main函数栈帧的大小与代码的行号及其对应的汇编指令。其中,$88-0表明了栈帧的大小及函数参数的大小,在第9章中会详细介绍栈大小、栈扩容函数runtime.morestack_noctxt的知识。关于汇编代码最前方获取TLS线程本地存储的操作,会在第15章介绍。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_36_3.jpg?sign=1738868413-m3QF0md8OuQGlELthyNIt4QyCG53VTdo-0-2cf964833cc7d8cf2329026de975ae1e)
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_37_1.jpg?sign=1738868413-xGRlwbfe1KU0tXoKnr48dfbam6orrp2z-0-88319d8f564963fe432ef3780acb0f0e)
在本书后面的章节中,还会经常通过查看汇编代码的方式来研究Go语言中某些特性的实现方式,正所谓汇编之下无秘密。