开始之前

  • 随着go1.17的发布,go的函数调用约定从之前的基于栈的形式改为了基于寄存器的形式:查看更新日志
  • 所以来对比一下1.16和1.17中函数调用栈的不同
  • 对比版本为1.17.1 darwin/amd64vs1.16.8 darwin/amd64

调用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

func main() {
	var (
		a, b, c = 1, 2, 3
	)
	x, y, z := add(a, b, c)
	all := x + y + z
	if all == 10 {

	}
}

func add(a, b, c int) (int, int, int) {
	return a+b, b+c, a+c
}

生成汇编go tool compile -S -N -l

  • -N disable optimizations 禁止优化
  • -S print assembly listing 列出汇编
  • -l disable inlining 禁止内联

1.16.8 的函数调用栈

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
    SUBQ    $136, SP            // 开辟136字节的栈空间
                                // SUBX a, b 表示将 b-=a
    MOVQ    BP, 128(SP)         // 将BP的值写入[128:136]
                                // MOVX a, b 表示将 a的值 写入 b
    LEAQ    128(SP), BP         // 将[128:136]的地址写入BP
                                // LEAX a, b 表示将 a的地址 写入 b
    MOVQ    $1, "".a+96(SP)     // 将1写入[96:104], 即[96:104]存储a(1)
    MOVQ    $2, "".b+80(SP)     // 将1写入[80:88], 即[80:86]存储b(2)
    MOVQ    $3, "".c+72(SP)     // 将1写入[72:80], 即[72:80]存储c(3)
    MOVQ    "".b+80(SP), AX     // 将[80:88]的值写入AX寄存器(b=2)
    MOVQ    "".a+96(SP), CX     // 将[96:108]的值写入CX寄存器(a=1)
    MOVQ    CX, (SP)            // 将CX寄存器的值写入[0:8](1,函数参数a)
    MOVQ    AX, 8(SP)           // 将AX寄存器的值写入[8:16](2,函数参数b)
    MOVQ    $3, 16(SP)          // 将1写如[16:24](3,函数参数c)
                                // 按照地址高低可以看到是c>b>a,所以说参数是从右到左入栈
                                // 此时的栈布局为
                                // ------------------------
                                //  top
                                //  BP            128:136
                                //  空闲           104:128
                                //  a             96:104
                                //  空闲           88:96
                                //  b             80:88
                                //  c             72:80
                                //  空闲           24:72
                                //  参数c          16:24
                                //  参数b          8:16
                                //  参数a          0:8     SP=0
                                // ------------------------
                                // 可以看见参数和返回都是在main栈中的
    CALL    "".add(SB)          // 调用add方法,这里会SP=SP-8,这个8个字节用来存储main的返回地址
                                                        // 由于add中没有局部变量,所以没有生成栈,直接操作了main栈中的参数和返回
                                                        // 初始化返回参数
                        MOVQ    $0, "".~r3+32(SP)       // 将0写入 [32:40] (main中[24:32]) 返回参数1
                        MOVQ    $0, "".~r4+40(SP)       // 将0写入 [40:48] (main中[32:40]) 返回参数2
                        MOVQ    $0, "".~r5+48(SP)       // 将0写入 [48:56] (main中[40:48]) 返回参数3
                                                        // 计算 a+b
                        MOVQ    "".a+8(SP), AX          // 将 [8:16] (main中[0:8]) 的值写入AX寄存器 (参数a)
                        ADDQ    "".b+16(SP), AX         // 将 [16:24] (main中[8:16]) (参数b) 的值与AX寄存器相加,然后再写入AX寄存器 (a+b)
                        MOVQ    AX, "".~r3+32(SP)       // 将AX寄存器的值写入 [32:40] (main中[24:32]) 返回参数1
                                                        // 计算 b+c
                        MOVQ    "".b+16(SP), AX         // 将 [16:24] (main中[8:16]) 的值写入AX寄存器 (参数b)
                        ADDQ    "".c+24(SP), AX         // 将 [24:32] (main中[16:24]) (参数c) 的值与AX寄存器相加,然后再写入AX寄存器
                        MOVQ    AX, "".~r4+40(SP)       // 将AX寄存器的值写入 [40:48] (main中[32:40]) 返回参数2
                                                        // 计算 a+c
                        MOVQ    "".a+8(SP), AX          // 将 [8:16]( main中[0:8]) 的值写入AX寄存器 (参数a)
                        ADDQ    "".c+24(SP), AX         // 将 [24:32] main中[16:24]) (参数c) 的值与AX寄存器相加,然后再写入AX寄存器
                        MOVQ    AX, "".~r5+48(SP)       // 将AX寄存器的值写入 [48:56] (main中[40:48]) 返回参数3
                                                        // 此时的栈布局为
                                                        // ------------------------
                                                        //  top
                                                        //  BP            128:136
                                                        //  空闲           104:128
                                                        //  a             96:104
                                                        //  空闲           88:96
                                                        //  b             80:88
                                                        //  c             72:80
                                                        //  空闲           48:72
                                                        //  返回参数3      40:48
                                                        //  返回参数2      32:40
                                                        //  返回参数1      24:32
                                                        //  参数c         16:24
                                                        //  参数b         8:16
                                                        //  参数a         0:8
                                                        //  返回地址       -8:0     SP=-8
                                                        // ------------------------
                        RET                             // 将SP返回到main中,SP+=8
    MOVQ    24(SP), AX                  // 将 [24:32] 的值写入AX寄存器 (返回值1)
    MOVQ    32(SP), CX                  // 将 [32:40] 的值写入CX寄存器 (返回值2)
    MOVQ    40(SP), DX                  // 将 [40:48] 的值写入DX寄存器 (返回值3)
    MOVQ    AX, ""..autotmp_7+120(SP)   // 将AX的值写入 [120:128] (临时变量x)
    MOVQ    CX, ""..autotmp_8+112(SP)   // 将CX的值写入 [112:120] (临时变量y)
    MOVQ    DX, ""..autotmp_9+104(SP)   // 将DX的值写入 [104:112] (临时变量z)
    MOVQ    ""..autotmp_7+120(SP), AX   // 将 [120:128] 的值写入 AX
    MOVQ    AX, "".x+64(SP)             // 将 AX的值写入 [64:72] (变量x)
    MOVQ    ""..autotmp_8+112(SP), AX   // 将 [112:120] 的值写入 AX
    MOVQ    AX, "".y+56(SP)             // 将 AX的值写入 [56:64] (变量y)
    MOVQ    ""..autotmp_9+104(SP), AX   // 将 [104:112] 的值写入 AX
    MOVQ    AX, "".z+48(SP)             // 将 AX的值写入 [48:56] (变量z)
    MOVQ    "".x+64(SP), AX             // 将 x的值写入AX
    ADDQ    "".y+56(SP), AX             // 计算 AX+=y
    ADDQ    "".z+48(SP), AX             // 计算 AX+=z
    MOVQ    AX, "".all+88(SP)           // 将AX的值写入 [88:96]
                                        // 此时的栈布局为
                                        // ------------------------
                                        //  top
                                        //  BP            128:136
                                        //  临时变量x      120:128
                                        //  临时变量y      112:120
                                        //  临时变量z      104:112
                                        //  a             96:104
                                        //  all           88:96
                                        //  b             80:88
                                        //  c             72:80
                                        //  x             64:72
                                        //  y             56:64
                                        //  z             48:56
                                        //  返回参数3      40:48
                                        //  返回参数2      32:40
                                        //  返回参数1      24:32
                                        //  参数c         16:24
                                        //  参数b         8:16
                                        //  参数a         0:8     SP=0
                                        // ------------------------
    CMPQ    AX, $10                     // 比较AX和10
    JEQ	200                             // 如果相同跳转到200行
    JMP	218                             // 如果不同,跳转到218行
    JMP	202                             // 跳转到202行
    MOVQ    128(SP), BP                 // 恢复BP的值
    ADDQ    $136, SP                    // 回收栈空间,此处只是回收,并没有清空
    RET                                 // 返回

可以看出来1.16.8中,函数调用的参数传递是放在调用方(caller)这里的

1.17.1 的函数调用栈

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
    SUBQ    $112, SP            // 开辟112字节的栈空间
    MOVQ    BP, 104(SP)         // 将BP的值写入[104:112]
    LEAQ    104(SP), BP         // 将[104:112]的地址写入BP
    MOVQ    $1, "".a+72(SP)     // [72:80]存储变量a=1
    MOVQ    $2, "".b+56(SP)     // [56:64]存储变量b=1
    MOVQ    $3, "".c+48(SP)     // [48:56]存储变量c=1
    MOVQ    "".b+56(SP), BX     // 将[56:64]的值写入BX寄存器
    MOVQ    "".a+72(SP), AX     // 将[72:80]的值写入AX寄存器
    MOVL    $3, CX              // 将1写入CX寄存器
                                // 此时的栈布局为
                                // ------------------------
                                //  top
                                //  BP            104:112
                                //  空闲           80:104
                                //  a             72:80
                                //  空闲           64:72
                                //  b             56:64
                                //  c             48:56
                                //  空闲           0:48    SP=0
                                // ------------------------
                                // 可以看出,相对于1.16,少了24个字节,可以猜测是参数或者返回值,因为
                                // 都是3个,所以这里看不出来(可以修改函数为2个返回,这样就可以明显知道了)
                                // 所以我们继续往下看是少了什么
    CALL    "".add(SB)          // 调用add方法,这里会SP=SP-8,这个8个字节用来存储main的返回地址
                        SUBQ    $32, SP             // 开辟一个32字节的栈空间,用来存储原来在调用方存储的数据
                                                    // 另外还需要存储BP,所以是 24+8=32 个字节
                        MOVQ    BP, 24(SP)          // 将BP的值写入 [24:32]
                        LEAQ    24(SP), BP          // 将 [24:32] 的地址写入 BP
                        MOVQ    AX, "".a+40(SP)     // 将AX寄存器的值写入 [40:48] (main中[0:8])
                        MOVQ    BX, "".b+48(SP)     // 将BX寄存器的值写入 [48:56] (main中[8:16])
                        MOVQ    CX, "".c+56(SP)     // 将CX寄存器的值写入 [56:64] (main中[16:24])
                        MOVQ    $0, "".~r3+16(SP)   // 将0写入 [16:24] 返回参数1
                        MOVQ    $0, "".~r4+8(SP)    // 将0写入 [8:16] 返回参数2
                        MOVQ    $0, "".~r5(SP)      // 将0写入 [0:8] 返回参数3
                                                    // 计算 a+b
                        MOVQ    "".a+40(SP), DX     // 将 [40:48] (main中[0:8]) 的值写入 DX寄存器 (参数a)
                        ADDQ    "".b+48(SP), DX     // 将 [48:56] (main中[8:16]) (参数b) 的值与DX寄存器相加,然后再写入DX寄存器
                        MOVQ    DX, "".~r3+16(SP)   // 将DX的值写入 [16:24] (返回值1)
                                                    // 计算 b+c
                        MOVQ    "".b+48(SP), DX     // 将 [48:56] (main中[8:16]) 的值写入 DX寄存器 (参数b)
                        ADDQ    "".c+56(SP), DX     // 将 [56:64] (main中[16:24]) (参数c) 的值与DX寄存器相加,然后再写入DX寄存器
                        MOVQ    DX, "".~r4+8(SP)    // 将DX的值写入 [8:16] (返回值2)
                                                    // 计算 a+c
                        MOVQ    "".a+40(SP), CX     // 将 [40:48] (main中[0:8]) 的值写入 DX寄存器 (参数a)
                        ADDQ    "".c+56(SP), CX     // 将 [56:64] (main中[16:24]) (参数c) 的值与DX寄存器相加,然后再写入DX寄存器
                        MOVQ    CX, "".~r5(SP)      // 将CX的值写入 [0:8] (返回值3)
                        MOVQ    "".~r3+16(SP), AX   // 将 [16:24] 的值写入 AX寄存器
                        MOVQ    "".~r4+8(SP), BX    // 将 [8:16] 的值写入 BX寄存器
                                                    // 此时的栈布局为
                                                    //  main栈帧---------------
                                                    //  top
                                                    //  BP            104:112
                                                    //  空闲           80:104
                                                    //  a             72:80
                                                    //  空闲           64:72
                                                    //  b             56:64
                                                    //  c             48:56
                                                    //  空闲           24:48
                                                    //  参数c          16:24
                                                    //  参数b          8:16
                                                    //  参数a          0:8
                                                    //  add方法返回地址 占8个字节
                                                    //  add栈帧----------------
                                                    //  BP            24:32
                                                    //  返回参数3       16:24
                                                    //  返回参数3       8:16
                                                    //  返回参数3       0:8     SP=0
                                                    // -----------------------
                                                    // 到这里我们就明白了,main中少的是返回值的存储
                                                    // 参数还是由原来的caller存储,返回值由callee存储,然后存入寄存器
                        MOVQ    24(SP), BP          // 恢复BP的值
                        ADDQ    $32, SP             // 回收栈空间
                        RET                         // 返回
    MOVQ    AX, ""..autotmp_7+96(SP)    // 将AX寄存器的值写入 [96:104] (临时变量x)
    MOVQ    BX, ""..autotmp_8+88(SP)    // 将BX寄存器的值写入 [88:96] (临时变量y)
    MOVQ    CX, ""..autotmp_9+80(SP)    // 将CX寄存器的值写入 [80:88] (临时变量z)
    MOVQ    ""..autotmp_7+96(SP), DX    // 将 [96:104] 的值写入 DX寄存器
    MOVQ    DX, "".x+40(SP)             // 将 DX寄存器的值写入 [40:48]
    MOVQ    ""..autotmp_8+88(SP), DX    // 将 [88:96] 的值写入 DX寄存器
    MOVQ    DX, "".y+32(SP)             // 将 DX寄存器的值写入 [32:40]
    MOVQ    ""..autotmp_9+80(SP), DX    // 将 [80:88] 的值写入 DX寄存器
    MOVQ    DX, "".z+24(SP)             // 将 DX寄存器的值写入 [24:32]
    MOVQ    "".x+40(SP), DX             // 将 x的值写入AX
    ADDQ    "".y+32(SP), DX             // 计算 AX+=y
    ADDQ    "".z+24(SP), DX             // 计算 AX+=z
    MOVQ    DX, "".all+64(SP)           // 将DX的值写入 [64:72]
                                        // 上面的步骤基本与1.16一致,除了返回值是直接从寄存器中读取的
                                        // 此时的栈布局为
                                        //  main栈帧---------------
                                        //  top
                                        //  BP            104:112
                                        //  临时变量x      96:104
                                        //  临时变量y      88:96
                                        //  临时变量z      80:88
                                        //  a             72:80
                                        //  all           64:72
                                        //  b             56:64
                                        //  c             48:56
                                        //  x             40:48
                                        //  y             32:40
                                        //  z             24:32
                                        //  参数c          16:24
                                        //  参数b          8:16
                                        //  参数a          0:8     SP=0
                                        // -----------------------
                                        // 剩下的与1.16一致

总结

1
2
3
4
5
可以看到明显的变化有两点
1. 参数的初始化由原来的caller变为了callee(参数的存储还是在caller中)
2. caller现在不再存储返回值,而是通过寄存器获取返回值:
    1.16 返回值->寄存器->临时变量->寄存器->变量
    1.17 寄存器->临时变量->寄存器->变量

参考