Interviewer: Let’s talk about the function call convention for Go

preface

Hello, everyone, I’m Asong. I haven’t updated it for a long time. Recently, I have been busy writing Python, so I am a little unfamiliar with the Go language, but I can’t give up the Go language. I still need to learn it. The call convention of the Go language was optimized in version 1.17. In this article, let’s take a look at what the call convention looks like for both versions.

Version 1.17 forward pass

Before Go1.17, the Go language function call was passed through the stack. Let’s use Go1.12 to write an example:

package main

func Test(a, b int) (int.int) {
	return a + b, a - b
}

func main(a) {
	Test(10.20)}Copy the code

Run the go tool compile -s -n -l main.go command to see the assembly, we are divided into two parts, first look at the main function part:

"".main STEXT size=68 args=0x0 locals=0x28
        0x0000 00000 (main.go:7)        TEXT    "".main(SB), ABIInternal, $400
        0x0000 00000 (main.go:7)        MOVQ    (TLS), CX
        0x0009 00009 (main.go:7)        CMPQ    SP, 16(CX)
        0x000d 00013 (main.go:7)        JLS     61
        0x000f 00015 (main.go:7)        SUBQ    $40, SP Allocate 40 bytes of stack space
        0x0013 00019 (main.go:7)        MOVQ    BP, 32(SP) // The base pointer is stored on the stack
        0x0018 00024 (main.go:7)        LEAQ    32(SP), BP
        0x001d 00029 (main.go:7)        FUNCDATA        $0, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x001d 00029 (main.go:7)        FUNCDATA        $1, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x001d 00029 (main.go:7)        FUNCDATA        $3, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x001d 00029 (main.go:8)        PCDATA  $2, $0
        0x001d 00029 (main.go:8)        PCDATA  $0, $0
        0x001d 00029 (main.go:8)        MOVQ    $10, (SP) // The first argument is pushed
        0x0025 00037 (main.go:8)        MOVQ    $20.8(SP) // The second argument is pushed
        0x002e 00046 (main.go:8)        CALL    "".Test(SB) // Call the function Test
        0x0033 00051 (main.go:9)        MOVQ    32(SP), BP // The Test function returns and restores the stack base pointer
        0x0038 00056 (main.go:9)        ADDQ    $40, SP // Destroy 40 bytes of stack memory
        0x003c 00060 (main.go:9)        RET / / return
        0x003d 00061 (main.go:9)        NOP
        0x003d 00061 (main.go:7)        PCDATA  $0, $- 1
        0x003d 00061 (main.go:7)        PCDATA  $2, $- 1
        0x003d 00061 (main.go:7)        CALL    runtime.morestack_noctxt(SB)
        0x0042 00066 (main.go:7)        JMP     0
        0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 2e 48eH.. %... H; a.v.H0x0010 83 ec 28 48 89 6c 24 20 48 8d 6c 24 20 48 c7 04  ..(H.l$ H.l$ H..
        0x0020 24 0a 00 00 00 48 c7 44 24 08 14 00 00 00 e8 00  $....H.D$.......
        0x0030 00 00 00 48 8b 6c 24 20 48 83 c4 28 c3 e8 00 00. H.l$ H.. (...0x0040 00 00 eb bc                                      ....
        rel 5+4 t=16 TLS+0
        rel 47+4 t=8 "".Test+0
        rel 62+4 t=8 runtime.morestack_noctxt+0
Copy the code

Parameters 10 and 20 are stacked from right to left, so the first parameter is stored at the top of the stack SP~SP+8, and the second parameter is stored at SP+8 ~SP+ 16. After the parameter is ready, the TEST function is called, and the corresponding assembly instruction is: CALL “”.test (SB), the corresponding assembly instruction is as follows:

"".Test STEXT nosplit size=49 args=0x20 locals=0x0
        0x0000 00000 (main.go:3)        TEXT    "".Test(SB), NOSPLIT|ABIInternal, $0- 32
        0x0000 00000 (main.go:3)        FUNCDATA        $0, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:3)        FUNCDATA        $1, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:3)        FUNCDATA        $3, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:3)        PCDATA  $2, $0
        0x0000 00000 (main.go:3)        PCDATA  $0, $0
        0x0000 00000 (main.go:3)        MOVQ    $0."".~r2+24(SP)SP+16 to SP+24 stores the first return value
        0x0009 00009 (main.go:3)        MOVQ    $0."".~r3+32(SP)
SP+24 to SP+32 stores the second return value
        0x0012 00018 (main.go:4)        MOVQ    "".a+8(SP), AX // Put the first argument into the AX register AX = 10
        0x0017 00023 (main.go:4)        ADDQ    "".b+16(SP), AX // Add the second argument to AX register AX = AX + 20 = 30
        0x001c 00028 (main.go:4)        MOVQ    AX, "".~r2+24(SP)
// the value in AX register is stored back on the stack: 24(SP)
        0x0021 00033 (main.go:4)        MOVQ    "".a+8(SP), AX
// Put the first argument into the AX register AX = 10
        0x0026 00038 (main.go:4)        SUBQ    "".b+16(SP), AX
AX = ax-20 = -10
        0x002b 00043 (main.go:4)        MOVQ    AX, "".~r3+32(SP)
// the value in AX register is stored back on the stack: 32(SP)
        0x0030 00048 (main.go:4)        RET // The function returns

Copy the code

From the above assembly instructions, we can draw a conclusion: Go language uses stack to pass parameters and receive return values, and multiple return values are also completed by allocating more memory.

This design based on stack passing parameters and receiving return values greatly reduces the implementation complexity, but sacrifices the performance of function calls. For example, C language uses both stack and register to pass parameters, which is better than Go language in performance. Let’s take a look at register passing parameters introduced by Go1.17.

Why register parameter passing is better than stack parameter passing

As we all know, CPU is the operation core and control core of a computer. Its main function is to interpret computer instructions and process data in computer software. The rough internal structure of CPU is as follows:

Is mainly composed of arithmetic unit and controller, arithmetic unit responsible for arithmetic operations or logic operations, register temporary hold will be the result of the unit after the processing of data and processing, back to the topic, is CPU registers internal components, and stored in the external, general CPU registers and read the speed of memory operation gap is orders of magnitude, when to data calculation, If the data is in memory, CPU need to copy the data from memory to the register to calculate, so for the stack passing parameters and receiving return value this invocation code, each computing needs from memory copy to register, is evaluated in the copy back to the memory, if you use the register and the cords, parameter will have on specific register in sequence, This reduces copying of data between memory and registers, which improves performance and provider performance.

If register passing is better than stack passing, why don’t all languages use register passing? Because registers differ from architecture to architecture, supporting register parameter passing requires compiler support, which makes the compiler more complex and difficult to maintain, and the number of registers is limited, and how parameters beyond the number of registers should be passed.

1.17 Register-based transfer

In version 1.17, the Go language designed a set of registrie-based call specifications. Currently, it only supports x86 platforms. Let’s take a look at a simple example:

func Test(a, b, c, d int) (int.int.int.int) {
	return a, b, c, d
}

func main(a)  {
	Test(1.2.3 ,4)}Copy the code

Run the go tool compile -s -n -l main.go command to see the assembly, we are divided into two parts, first look at the main function part:

"".main STEXT size=62 args=0x0 locals=0x28 funcid=0x0
        0x0000 00000 (main.go:7)        TEXT    "".main(SB), ABIInternal, $400
        0x0000 00000 (main.go:7)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:7)        PCDATA  $0, $2 -
        0x0004 00004 (main.go:7)        JLS     55
        0x0006 00006 (main.go:7)        PCDATA  $0, $- 1
        0x0006 00006 (main.go:7)        SUBQ    $40, SP// Allocate 40 bytes of stack space, and store the base address pointer on the stack
        0x000a 00010 (main.go:7)        MOVQ    BP, 32(SP)// The base pointer is stored on the stack
        0x000f 00015 (main.go:7)        LEAQ    32(SP), BP
        0x0014 00020 (main.go:7)        FUNCDATA        $0, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (main.go:7)        FUNCDATA        $1, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (main.go:8)        MOVL    $1, AX // Parameter 1 is passed in the AX register
        0x0019 00025 (main.go:8)        MOVL    $2, BX // Parameter 2 is passed using the BX register
        0x001e 00030 (main.go:8)        MOVL    $3, CX // Parameter 3 is passed using the CX register
        0x0023 00035 (main.go:8)        MOVL    $4, DI // Parameter 4 is passed using the DI register
        0x0028 00040 (main.go:8)        PCDATA  $1, $0
        0x0028 00040 (main.go:8)        CALL    "".Test(SB) // Call Test
        0x002d 00045 (main.go:9)        MOVQ    32(SP), BP // The Test function returns and restores the stack base pointer
        0x0032 00050 (main.go:9)        ADDQ    $40, SP // Destroy 40 bytes of stack memory
        0x0036 00054 (main.go:9)        RET / / return

Copy the code

CALL “”. TEST (SB) : CALL “”. TEST (SB) : CALL “”.

"".Test STEXT nosplit size=133 args=0x20 locals=0x28 funcid=0x0
        0x0000 00000 (main.go:3)        TEXT    "".Test(SB), NOSPLIT|ABIInternal, $40- 32
        0x0000 00000 (main.go:3)        SUBQ    $40, SP
        0x0004 00004 (main.go:3)        MOVQ    BP, 32(SP)
        0x0009 00009 (main.go:3)        LEAQ    32(SP), BP
        0x000e 00014 (main.go:3)        FUNCDATA        $0, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:3)        FUNCDATA        $1, gclocals33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:3)        FUNCDATA        $5."".Test.arginfo1(SB)
0x000e 00014 (main.go:3)        MOVQ    AX, "".a+48(SP) // select 1 from register AX and put it on stack 48(SP)
0x0013 00019 (main.go:3)        MOVQ    BX, "".b+56(SP) // get parameter 2 from register BX 56(SP)
0x0018 00024 (main.go:3)        MOVQ    CX, "".c+64(SP) // select * from CX;
0x001d 00029 (main.go:3)        MOVQ    DI, "".d+72(SP) // select * from DI;
        0x0022 00034 (main.go:3)        MOVQ    $0."".~r4+24(SP)
        0x002b 00043 (main.go:3)        MOVQ    $0."".~r5+16(SP)
        0x0034 00052 (main.go:3)        MOVQ    $0."".~r6+8(SP)
        0x003d 00061 (main.go:3)        MOVQ    $0."".~r7(SP)
        0x0045 00069 (main.go:4)        MOVQ    "".a+48(SP), DX // The following operation returns the value into a register
        0x004a 00074 (main.go:4)        MOVQ    DX, "".~r4+24(SP)
        0x004f 00079 (main.go:4)        MOVQ    "".b+56(SP), DX
        0x0054 00084 (main.go:4)        MOVQ    DX, "".~r5+16(SP)
        0x0059 00089 (main.go:4)        MOVQ    "".c+64(SP), DX
        0x005e 00094 (main.go:4)        MOVQ    DX, "".~r6+8(SP)
        0x0063 00099 (main.go:4)        MOVQ    "".d+72(SP), DI
        0x0068 00104 (main.go:4)        MOVQ    DI, "".~r7(SP)
        0x006c 00108 (main.go:4)        MOVQ    "".~r4+24(SP), AX
        0x0071 00113 (main.go:4)        MOVQ    "".~r5+16(SP), BX
        0x0076 00118 (main.go:4)        MOVQ    "".~r6+8(SP), CX
        0x007b 00123 (main.go:4)        MOVQ    32(SP), BP
        0x0080 00128 (main.go:4)        ADDQ    $40, SP
        0x0084 00132 (main.go:4)        RET

Copy the code

Both parameters and returns are passed in registers, and the return values and inputs use exactly the same sequence of registers, and in the same order.

Because of this optimization, in the case of some function calls with deep nesting level, the probability of memory will be reduced, if you have the opportunity to do pressure test can try ~.

conclusion

To master and understand the call process of the function is an important lesson for us to further study the Go language. After reading this article, I hope you have mastered the call convention of the function.

Well, that’s the end of this article, I’m Asong, and I’ll see you next time.

Welcome to the public account: Golang Dream Factory