go 语言 runtime(包括调度器)源代码中有部分代码是用汇编语言编写的,不过这些汇编代码并非针对特定体系结构的汇编代码,而是 go 语言引入的一种伪汇编,它同样也需要经过汇编器转换成机器指令才能被 CPU 执行。需要注意的是,用 go 汇编语言编写的代码一旦经过汇编器转换成机器指令之后,再用调试工具反汇编出来的代码已经不是 go 语言汇编代码了,而是跟平台相关的汇编代码。

go 汇编格式跟前面讨论过的 AT&T 汇编基本上差不多,但也有些重要区别,本节就这些差异做一个简单说明。

寄存器

go 汇编语言中使用的寄存器的名字与 AMD64 不太一样,下表显示了它们之间的对应关系:

go语言调度器源代码情景分析之六:go汇编语言 - 爱写程序的阿波张 - 博客园 - 图1

除了这些跟 AMD64 CPU 硬件寄存器一一对应的寄存器外,go 汇编还引入了几个没有任何硬件寄存器与之对应的虚拟寄存器,这些寄存器一般用来存放内存地址,引入它们的主要目的是为了方便程序员和编译器用来定位内存中的代码和数据。

下面重点介绍在 go 汇编中常见的 2 个虚拟寄存器的使用方法:

FP 虚拟寄存器: 主要用来引用函数参数。go 语言规定函数调用时参数都必须放在栈上,比如被调用函数使用 first_arg+0(FP) 来引用调用者传递进来的第一个参数,用 second_arg+8(FP) 来引用第二个参数 ,以此类推,这里的 first_arg 和 second_arg 仅仅是一个帮助我们阅读源代码的符号,对编译器来说无实际意义,+0 和 + 8 表示相对于 FP 寄存器的偏移量。我们用一个 runtime 中的函数片段作为例子来看看 FP 的使用。

go runtime 中有一个叫 gogo 的函数,它接受一个 gobuf 类型的指针

// func gogo(buf *gobuf)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB), NOSPLIT, $16-8 MOVQbuf+0(FP), BX// gobuf —>bx
……

MOVQ buf+0(FP), BX 这一条指令把调用者传递进来的指针 buf 放入 BX 寄存器中,可以看到,在 gogo 函数是通过 buf+0(FP) 这种方式获取到参数的。从被调用函数(此处为 gogo 函数)的角度来看,FP 与函数栈帧之间的关系如下图,可以看出 FP 寄存器指向调用者的栈帧,而不是被调用函数的栈帧。

go语言调度器源代码情景分析之六:go汇编语言 - 爱写程序的阿波张 - 博客园 - 图2

SB 虚拟寄存器: 保存程序地址空间的起始地址。还记得在函数调用栈一节我们看过的进程在内存中的布局那张图吗,这个 SB 寄存器保存的值就是代码区的起始地址,它主要用来定位全局符号。go 汇编中的函数定义、函数调用、全局变量定义以及对其引用会用到这个 SB 虚拟寄存器。对于这个虚拟寄存器,我们不用过多的关注,在代码中看到它时知道它是一个虚拟寄存器就行了。

操作码

AT&T 格式的寄存器操作码一般使用小写且寄存器的名字前面有个 % 符号,而 go 汇编使用大写而且寄存器名字前没有 % 符号,比如:

AT&T 格式 mov %rbp,%rsp

go 汇编格式

MOVQ BP,SP

操作数宽度(即操作数的位数)

AT&T 格式的汇编指令中如果有寄存器操作数,则根据寄存器的名字(比如 rax, eax, ax, al 分别代表 64,32,16 和 8 位寄存器)就可以确定操作数到底是多少位(8,16,32 还是 64 位),所以不需要操作码后缀,如果没有寄存器操作数又是访存指令的话,则操作码需要加上后缀 b、w、l 或 q 来指定到底存取内存中的多少个字节。

而 go 汇编中,寄存器的名字没有位数之分,比如 AX 寄存器没有什么 RAX, EAX 之类的名字,指令中一律只能使用 AX。所以如果指令中有操作数寄存器或是指令需要访问内存,则操作码都需要带上后缀 B(8 位)、W(16 位)、D(32 位) 或 Q(64 位)

函数定义

还是以 go runtime 中的 gogo 函数为例:

// func gogo(buf *gobuf)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB), NOSPLIT, $16-8 ……

下面对这个函数定义的第一行的各部分做个说明:

TEXT runtime·gogo(SB): 指明在代码区定义了一个名字叫 gogo 的全局函数(符号),该函数属于 runtime 包。

NOSPLIT: 指示编译器不要在这个函数中插入检查栈是否溢出的代码。

$16-8: 数字 16 说明此函数的栈帧大小为 16 字节,8 说明此函数的参数和返回值一共需要占用 8 字节内存。因为这里的 gogo 函数没有返回值,只有一个指针参数,对于 AMD64 平台来说指针就是 8 字节。go 语言中函数调用的参数和函数返回值都是放在栈上的,而且这部分栈内存是由调用者而非被调用函数负责预留,所以在函数定义时需要说明到底需要在调用者的栈帧中预留多少空间。

go 汇编还有一些用法比较特别的地方,现在不讨论,等我们分析源代码遇到它们时再结合上下文做详细说明。
https://www.cnblogs.com/abozhang/p/10781682.html