在go 1.17,函数调用规约有更新,会使用九个寄存次存放参数和返回值(因为寄存器速度更快),而在go1.17之前,函数调用时的参数与返回值都会放在栈上,所以分别用go1.17和go1.16.4来验证下
测试代码如下:
package main
func main() {
callee(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}
func callee(a, b, c, d, e, f, g, h, i, j int) (int, int, int, int, int, int, int, int, int, int) {
localVar := 1 // 为了让接下来画callee的栈让读者更好理解,这里故意加了一个本地变量
_ = localVar
return 1, 2, 3, 4, 5, 6, 7, 8, 9, localVar
}
1.16.4
执行 go tool compile -S -N -l main.go
输出如下(删除了不重要的细节):
"".main STEXT size=170 args=0x0 locals=0xa8 funcid=0x0
(...)
0x002f 00047 (main.go:4) MOVQ $1, (SP) // 传递给callee的第一个参数
0x0037 00055 (main.go:4) MOVQ $2, 8(SP)
0x0040 00064 (main.go:4) MOVQ $3, 16(SP)
0x0049 00073 (main.go:4) MOVQ $4, 24(SP)
0x0052 00082 (main.go:4) MOVQ $5, 32(SP)
0x005b 00091 (main.go:4) MOVQ $6, 40(SP)
0x0064 00100 (main.go:4) MOVQ $7, 48(SP)
0x006d 00109 (main.go:4) MOVQ $8, 56(SP)
0x0076 00118 (main.go:4) MOVQ $9, 64(SP)
0x007f 00127 (main.go:4) MOVQ $10, 72(SP) // 传递给callee的最后一个参数
(...)
"".callee STEXT nosplit size=254 args=0xa0 locals=0x10 funcid=0x0
(...)
0x007d 00125 (main.go:8) MOVQ $1, "".localVar(SP)
0x0085 00133 (main.go:10) MOVQ $1, "".~r10+104(SP) // 本地变量
0x008e 00142 (main.go:10) MOVQ $2, "".~r11+112(SP) // 第一个返回值
0x0097 00151 (main.go:10) MOVQ $3, "".~r12+120(SP)
0x00a0 00160 (main.go:10) MOVQ $4, "".~r13+128(SP)
0x00ac 00172 (main.go:10) MOVQ $5, "".~r14+136(SP)
0x00b8 00184 (main.go:10) MOVQ $6, "".~r15+144(SP)
0x00c4 00196 (main.go:10) MOVQ $7, "".~r16+152(SP)
0x00d0 00208 (main.go:10) MOVQ $8, "".~r17+160(SP)
0x00dc 00220 (main.go:10) MOVQ $9, "".~r18+168(SP) // 最后一个返回值
0x00e8 00232 (main.go:10) MOVQ "".localVar(SP), AX
(...)
main和callee的栈布局如下:
可以看出,1,16版本的go是通过栈传递所有的参数和返回值的
1.17
执行 go tool compile -S -l main.go 这里没有加上-N
"".main STEXT size=103 args=0x0 locals=0x60 funcid=0x0
(...)
0x0014 00020 (main.go:4) MOVQ $10, (SP) // 第10个参数
0x001c 00028 (main.go:4) MOVL $1, AX // 第1个参数
0x0021 00033 (main.go:4) MOVL $2, BX
0x0026 00038 (main.go:4) MOVL $3, CX
0x002b 00043 (main.go:4) MOVL $4, DI
0x0030 00048 (main.go:4) MOVL $5, SI
0x0035 00053 (main.go:4) MOVL $6, R8
0x003b 00059 (main.go:4) MOVL $7, R9
0x0041 00065 (main.go:4) MOVL $8, R10
0x0047 00071 (main.go:4) MOVL $9, R11 // 第9个参数
(...)
"".callee STEXT nosplit size=59 args=0x58 locals=0x0 funcid=0x0
(...)
0x0000 00000 (main.go:10) MOVQ $10, "".~r19+16(SP) // 第10个返回值
0x0009 00009 (main.go:10) MOVL $1, AX // 第1个返回值
0x000e 00014 (main.go:10) MOVL $2, BX
0x0013 00019 (main.go:10) MOVL $3, CX
0x0018 00024 (main.go:10) MOVL $4, DI
0x001d 00029 (main.go:10) MOVL $5, SI
0x0022 00034 (main.go:10) MOVL $6, R8
0x0028 00040 (main.go:10) MOVL $7, R9
0x002e 00046 (main.go:10) MOVL $8, R10
0x0034 00052 (main.go:10) MOVL $9, R11 // 第9个返回值
0x003a 00058 (main.go:10) RET
(...)
可以看到,官方只使用了 9 个通用寄存器,依次是 AX,BX,CX,DI,SI,R8,R9,R10,R11,超出部分,按顺序放在栈上。