官方博客: https://go.googlesource.com/proposal/+/master/design/40724-register-calling.md
测试平台:
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
在go1.17之前,go的函数调用惯例是通过栈来传递参数、返回值。在go1.17的实现中,改变了这一惯例,采用寄存器的方式传递参数、返回值。
下面做一个小测试看看效果,分别采用两个go版本,编译相同的代码,查看汇编指令证明官方的说法,主要是用于学习。
go版本:
- 1.16.2(作为对照)
- 1.17.3
一个小的测试demo
package main
func main() {
i := 1024
j := test(i)
println(j)
}
//go:noinline
func test(i int) int {
return i + 1
}
使用1.16.2进行编译(go tool compile -S main.go
),省略不相关输出
"".test STEXT nosplit size=14 args=0x10 locals=0x0 funcid=0x0
0x0000 00000 (main.go:10) TEXT "".test(SB), NOSPLIT|ABIInternal, $0-16
0x0000 00000 (main.go:10) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:10) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:11) MOVQ "".i+8(SP), AX // 这里将栈上存储的参数复制到寄存器AX
0x0005 00005 (main.go:11) INCQ AX // 对寄存器AX上的数执行自增操作
0x0008 00008 (main.go:11) MOVQ AX, "".~r1+16(SP) // 将寄存器AX上的数,复制到目标返回值位置(栈上)
0x000d 00013 (main.go:11) RET
0x0000 48 8b 44 24 08 48 ff c0 48 89 44 24 10 c3 H.D$.H..H.D$..
使用1.17.3编译:
"".test STEXT nosplit size=4 args=0x8 locals=0x0 funcid=0x0
0x0000 00000 (main.go:10) TEXT "".test(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:10) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:10) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:10) FUNCDATA $5, "".test.arginfo1(SB)
0x0000 00000 (main.go:11) INCQ AX // 直接操作寄存器,并约定通过寄存器AX返回数据给调用方
0x0003 00003 (main.go:11) RET
0x0000 48 ff c0 c3
下面看看多个返回值(以两个为例),go编译器怎么处理(1.16.2就不做演示了,主要看新版本的编译行为)。代码如下:
package main
func main() {
i := 1024
j, k := test(i)
println(j, k)
}
//go:noinline
func test(i int) (int, int) {
return i + 1, i + 2
}
"".test STEXT nosplit size=12 args=0x8 locals=0x0 funcid=0x0
0x0000 00000 (main.go:10) TEXT "".test(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:10) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:10) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:10) FUNCDATA $5, "".test.arginfo1(SB)
0x0000 00000 (main.go:11) LEAQ 1(AX), CX // CX = AX + 1
0x0004 00004 (main.go:11) LEAQ 2(AX), BX // BX = AX + 2
0x0008 00008 (main.go:11) MOVQ CX, AX // AX = CX
0x000b 00011 (main.go:11) RET
0x0000 48 8d 48 01 48 8d 58 02 48 89 c8 c3 H.H.H.X.H...
可见第一个返回值放在AX寄存器,第二个返回值放在BX。这样看来,多个返回值也会通过寄存器传递,我们来试试再多一些返回值,会是怎么样的(比如7个),代码如下:
package main
func main() {
i := 1024
println(test(i))
}
//go:noinline
func test(i int) (int, int, int, int, int, int, int) {
return i + 1, i + 2, i + 3, i + 4, i + 5, i+ 6, i + 7
}
"".test STEXT nosplit size=33 args=0x8 locals=0x0 funcid=0x0
0x0000 00000 (main.go:9) TEXT "".test(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:9) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $5, "".test.arginfo1(SB)
0x0000 00000 (main.go:10) LEAQ 1(AX), DX
0x0004 00004 (main.go:10) LEAQ 2(AX), BX
0x0008 00008 (main.go:10) LEAQ 3(AX), CX
0x000c 00012 (main.go:10) LEAQ 4(AX), DI
0x0010 00016 (main.go:10) LEAQ 5(AX), SI
0x0014 00020 (main.go:10) LEAQ 6(AX), R8
0x0018 00024 (main.go:10) LEAQ 7(AX), R9
0x001c 00028 (main.go:10) MOVQ DX, AX
0x001f 00031 (main.go:10) NOP
0x0020 00032 (main.go:10) RET
0x0000 48 8d 50 01 48 8d 58 02 48 8d 48 03 48 8d 78 04 H.P.H.X.H.H.H.x.
0x0010 48 8d 70 05 4c 8d 40 06 4c 8d 48 07 48 89 d0 90 H.p.L.@.L.H.H...
0x0020 c3
看来7个返回值也都会通过寄存器传递,按照返回值的顺序使用寄存器的顺序是: AX、BX、CX、DI、SI、R8、R9
再次增加返回值数量,达到14个
package main
func main() {
i := 1024
println(test(i))
}
//go:noinline
func test(i int) (int, int, int, int, int, int, int, int, int, int, int, int, int, int) {
return i + 1, i + 2, i + 3, i + 4, i + 5, i+ 6, i + 7, i + 8, i + 9, i + 10, i + 11, i + 12, i+ 13, i + 14
}
"".test STEXT nosplit size=85 args=0x30 locals=0x0 funcid=0x0
0x0000 00000 (main.go:9) TEXT "".test(SB), NOSPLIT|ABIInternal, $0-48
0x0000 00000 (main.go:9) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $5, "".test.arginfo1(SB)
0x0000 00000 (main.go:10) LEAQ 10(AX), DX
0x0004 00004 (main.go:10) MOVQ DX, "".~r10+8(SP)
0x0009 00009 (main.go:10) LEAQ 11(AX), DX
0x000d 00013 (main.go:10) MOVQ DX, "".~r11+16(SP)
0x0012 00018 (main.go:10) LEAQ 12(AX), DX
0x0016 00022 (main.go:10) MOVQ DX, "".~r12+24(SP)
0x001b 00027 (main.go:10) LEAQ 13(AX), DX
0x001f 00031 (main.go:10) MOVQ DX, "".~r13+32(SP)
0x0024 00036 (main.go:10) LEAQ 14(AX), DX
0x0028 00040 (main.go:10) MOVQ DX, "".~r14+40(SP)
0x002d 00045 (main.go:10) LEAQ 1(AX), DX
0x0031 00049 (main.go:10) LEAQ 2(AX), BX
0x0035 00053 (main.go:10) LEAQ 3(AX), CX
0x0039 00057 (main.go:10) LEAQ 4(AX), DI
0x003d 00061 (main.go:10) LEAQ 5(AX), SI
0x0041 00065 (main.go:10) LEAQ 6(AX), R8
0x0045 00069 (main.go:10) LEAQ 7(AX), R9
0x0049 00073 (main.go:10) LEAQ 8(AX), R10
0x004d 00077 (main.go:10) LEAQ 9(AX), R11
0x0051 00081 (main.go:10) MOVQ DX, AX
0x0054 00084 (main.go:10) RET
0x0000 48 8d 50 0a 48 89 54 24 08 48 8d 50 0b 48 89 54 H.P.H.T$.H.P.H.T
0x0010 24 10 48 8d 50 0c 48 89 54 24 18 48 8d 50 0d 48 $.H.P.H.T$.H.P.H
0x0020 89 54 24 20 48 8d 50 0e 48 89 54 24 28 48 8d 50 .T$ H.P.H.T$(H.P
0x0030 01 48 8d 58 02 48 8d 48 03 48 8d 78 04 48 8d 70 .H.X.H.H.H.x.H.p
0x0040 05 4c 8d 40 06 4c 8d 48 07 4c 8d 50 08 4c 8d 58 .L.@.L.H.L.P.L.X
0x0050 09 48 89 d0 c3 .H...
能看出,go支持寄存器传递返回值的最大数量是9个,如果返回值多余9个,剩余的返回值还是通过栈传递。在执行顺序上,本例中先处理第10个及以后的返回值,然后处理前9个返回值。按照返回值的顺序使用寄存器的顺序是: AX、BX、CX、DI、SI、R8、R9、R10、R11
多个返回值使用寄存器的情况,我们弄清楚了,下面看看多参数的情况,这次直接使用11个参数看看。
package main
func main() {
println(test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
}
//go:noinline
func test(i, j, k, l, m, n, p, q, r, s, t int) (int) {
return i + j + k + l + m + n + p + q + r + s + t
}
"".main STEXT size=141 args=0x0 locals=0x68 funcid=0x0
...
0x0014 00020 (main.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0014 00020 (main.go:4) MOVQ $10, (SP)
0x001c 00028 (main.go:4) MOVQ $11, 8(SP)
0x0025 00037 (main.go:4) MOVL $1, AX
0x002a 00042 (main.go:4) MOVL $2, BX
0x002f 00047 (main.go:4) MOVL $3, CX
0x0034 00052 (main.go:4) MOVL $4, DI
0x0039 00057 (main.go:4) MOVL $5, SI
0x003e 00062 (main.go:4) MOVL $6, R8
0x0044 00068 (main.go:4) MOVL $7, R9
0x004a 00074 (main.go:4) MOVL $8, R10
0x0050 00080 (main.go:4) MOVL $9, R11
0x0056 00086 (main.go:4) PCDATA $1, $0
0x0056 00086 (main.go:4) CALL "".test(SB)
0x005b 00091 (main.go:4) MOVQ AX, ""..autotmp_1+88(SP)
...
"".test STEXT nosplit size=43 args=0x58 locals=0x0 funcid=0x0
...
0x0000 00000 (main.go:8) FUNCDATA $5, "".test.arginfo1(SB)
0x0000 00000 (main.go:9) LEAQ (BX)(AX*1), DX
0x0004 00004 (main.go:9) ADDQ DX, CX
0x0007 00007 (main.go:9) ADDQ DI, CX
0x000a 00010 (main.go:9) ADDQ SI, CX
0x000d 00013 (main.go:9) ADDQ R8, CX
0x0010 00016 (main.go:9) ADDQ R9, CX
0x0013 00019 (main.go:9) ADDQ R10, CX
0x0016 00022 (main.go:9) ADDQ R11, CX
0x0019 00025 (main.go:9) MOVQ "".s+8(SP), DX
0x001e 00030 (main.go:9) ADDQ DX, CX
0x0021 00033 (main.go:9) MOVQ "".t+16(SP), DX
0x0026 00038 (main.go:9) LEAQ (DX)(CX*1), AX
0x002a 00042 (main.go:9) RET
0x0000 48 8d 14 03 48 01 d1 48 01 f9 48 01 f1 4c 01 c1 H...H..H..H..L..
可以看到,前9个参数通过寄存器传递,后面的参数通过栈传递;另外参数的顺序对应使用寄存器的顺序为:AX、BX、CX、DI、SI、R8、R9、R10、R11
从上面可以得出结论,在当前系统架构、go语言版本的情况下,go编译器会将前9个参数、前9个返回值通过寄存器传递,多出的参数、返回值则通过栈进行传递。