Go Function Calls Stack and Register Perspectives

Keywords: Go Programming less Java

The main point of calling a function is to pass parameters and return values using registers and memory frame stacks.Although Go is a compiled language, there are some differences in the use of registers and stacks compared to C. Go has its own protocol and introduces statements such as defer, which makes the call process more complex.Understanding the procedure of Go function calls in the CPU instruction layer can help you write efficient code and quickly identify key points when optimizing performance and Bug checking.This paper demonstrates the call process of Go with a short sample code and corresponding assembly code, shows the actual transfer process of parameters of different data types, and analyses the actual data structure in memory when anonymous functions, closures are passed as parameters or return values.This article does not expand on the use and implementation details of the stack by the protocol.
To read this article, you need to have a basic knowledge of computer architecture (at least program memory layout, stacks, registers), and the Go basic syntax.Reference documents provide a more detailed knowledge of these topics.
The following:

term

  • Stack: Each process/thread/goroutine has its own call stack, parameter and return value transfers, and local variable storage of functions is usually done through the stack.Like stacks in data structures, memory stacks are LIFO, with addresses growing from high addresses to low addresses.
  • stack frame: Also known as frame.A stack consists of many frames that describe the call relationships between functions.Each frame corresponds to a function call that has not yet been returned, and the frame itself stores data as a stack.
  • Caller caller
  • Callee callee, such as calling function B in function A, A is caller, B is callee

Register (X86)

  • ESP: An extended stack pointer that holds a pointer to the top of the stack at the top of the stack frame (that is, the stack of currently executing functions).Be careful:

    • ESP points to an address in memory that already stores content, not an idle address.For example, from 0xC0000000 to 0xC00000FF is the stack space already used, ESP points to 0xC00000FF
  • EBP: An extended base pointer, also known as a frame pointer, holds a pointer that points to the bottom of the top stack frame on the stack.
  • EIP: The register stores the memory address of the next CPU instruction. When the CPU has executed the current instruction, it reads the memory address of the next instruction from the EIP register, and then proceeds.

Note: 16-bit registers have no prefix (SP, BP, IP), 32-bit prefix is E(ESP, EBP, EIP), and 64-bit prefix is R(RSP, RBP, RIP)

Assembly Instructions

  • PUSH: A stack-up instruction that first decreases ESP by 4 when executed, then writes the contents to the stack memory pointed by the ESP.
  • POP: Out-of-stack instructions, POP instructions are executed by first reading out a word-long content of stack memory that ESP points to, then adding ESP to 4.Be careful:

    • When using PUSH and POP instructions, the stack can only be accessed by word, not byte.
  • CALL: Calls a function instruction, returns the address (the next instruction in the call instruction) stack, then jumps to the function entry.
  • RET: Return the instruction, eject the top return address to the EIP, and then proceed according to the EIP.
  • LEAVE: Equivalent to mov esp,ebp; pop ebp;
  • MOVL: Transfer values between memory and registers, registers and registers
  • LEAL: The operand used to assign a memory address directly to the destination

Note: 8-bit instruction suffix is B, 16-bit is S, 32-bit is L, 64-bit is Q

Call Convention

A call convention is a scheme for how functions are called in a program to pass parameters, allocate and clean stacks, etc.The contents of a calling convention include:

  • Whether parameters are passed through registers, stacks, or a mixture of both
  • Is a parameter passed through a stack left-to-right or right-to-left stack
  • Is the result of the function passed by register or stack
  • Whether caller or callee cleans up stack space
  • Which registers should the callee save for the caller

    For example, the calling convention for C (cdecl, C declaration) is:

  • Function arguments are stacked on the thread stack in right-to-left order.
  • Function results are saved in register EAX/AX/AL
  • Floating-point results are stored in register ST0
  • The compiled function name is prefixed with an underscore character
  • The caller is responsible for popping arguments (that is, stacks) from the thread stack
  • The reshaping argument of 8 or 16 bit length is promoted to 32 bit length.
  • volatile registers affected by function calls: EAX, ECX, EDX, ST0 - ST7, ES, GS
  • Registers unaffected by function calls: EBX, EBP, ESP, EDI, ESI, CS, DS
  • RET directives are returned from the callee of the function to the caller (essentially reading the return address of the function saved at the thread stack indicated by register EBP and loading it into the IP register)

Cdecl saves function return values in registers, so C does not support multiple return values.In addition, cdecl is the caller's responsibility for stacking, thus enabling functions with variable parameters.Functions with variable parameters cannot be implemented if the callee is responsible for cleanup, but compiling the code is more efficient because the code that cleans up the stack does not need to be generated once per call (compiler calculation).(The ret directive for x86 allows an optional 16-bit parameter to specify the number of stack bytes used to unstack before returning to the caller.Code like ret 12, if you encounter such assembly code, it means the callee stack.)

Note that although C uses registers to pass return values, the size of return values can be handled differently.If it is less than 4 bytes, the return value is stored in the eax register and the eax is read by the function caller.If the return value is 5 to 8 bytes, it is returned by eax in conjunction with edx.If it is larger than 8 bytes, an extra space temp is first opened up on the stack, and the address of the temp object is stacked as a hidden parameter.When the function returns, the data is copied to the temp object and the address of the temp object is passed out using register eax.The caller copies the content of the temp object pointed to by eax.

You can see that when designing the features of a programming language, you need to choose the appropriate calling convention for them to implement them at the bottom level.(Calling conventions are chosen by the compiler of the programming language, and compilers with different languages may choose to implement different calling conventions)

A typical C function call

In caller:

  • Put arguments on the right-to-left stack (under X86-64: write arguments to registers, if more than 6 arguments, more than the right-to-left stack)
  • Execute the call command (returns the address stack and jumps to the callee entry)

Enter the callee:

  • Push ebp; mov ebp, esp; at this point EBP and ESP already represent the bottom and top of the callee, respectively.The EBP value then remains fixed.Both local variables and temporary storage can then be found using the benchmark pointer EBP plus offset.
  • sub xxx, esp; move down the top of the stack, allocate space for callee, store local variables, etc.Allocated memory units can be accessed through EBP-K or ESP+K.
  • Stack the values of some registers (possible)
  • callee execution
  • Pop some register values out of the stack (possible)
  • mov esp,ebp; pop ebp; (these two instructions can also be replaced with the leave directive) EBP and ESP return to the state before they entered the callee, that is, the bottom and top states of the caller, respectively.
  • Execute ret directive

Back to the code in the caller

int add(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8) {
    return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
}

int main() {
    int i = add(1, 2, 3 , 4, 5, 6, 7, 8);
}

x86 Version Compilation

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 14    sdk_version 10, 14
    .globl    _add                    ## -- Begin function add
    .p2align    4, 0x90
_add:                                   ## @add
    .cfi_startproc
## %bb.0:
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset %ebp, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register %ebp
    pushl    %ebx
    pushl    %edi
    pushl    %esi
    subl    $32, %esp
    .cfi_offset %esi, -20
    .cfi_offset %edi, -16
    .cfi_offset %ebx, -12
    movl    36(%ebp), %eax
    movl    32(%ebp), %ecx
    movl    28(%ebp), %edx
    movl    24(%ebp), %esi
    movl    20(%ebp), %edi
    movl    16(%ebp), %ebx
    movl    %eax, -16(%ebp)         ## 4-byte Spill
    movl    12(%ebp), %eax
    movl    %eax, -20(%ebp)         ## 4-byte Spill
    movl    8(%ebp), %eax
    movl    %eax, -24(%ebp)         ## 4-byte Spill
    movl    8(%ebp), %eax
    addl    12(%ebp), %eax
    addl    16(%ebp), %eax
    addl    20(%ebp), %eax
    addl    24(%ebp), %eax
    addl    28(%ebp), %eax
    addl    32(%ebp), %eax
    addl    36(%ebp), %eax
    movl    %ebx, -28(%ebp)         ## 4-byte Spill
    movl    %ecx, -32(%ebp)         ## 4-byte Spill
    movl    %edx, -36(%ebp)         ## 4-byte Spill
    movl    %esi, -40(%ebp)         ## 4-byte Spill
    movl    %edi, -44(%ebp)         ## 4-byte Spill
    addl    $32, %esp
    popl    %esi
    popl    %edi
    popl    %ebx
    popl    %ebp
    retl
    .cfi_endproc
                                        ## -- End function
    .globl    _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset %ebp, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register %ebp
    subl    $40, %esp
    movl    $1, (%esp)
    movl    $2, 4(%esp)
    movl    $3, 8(%esp)
    movl    $4, 12(%esp)
    movl    $5, 16(%esp)
    movl    $6, 20(%esp)
    movl    $7, 24(%esp)
    movl    $8, 28(%esp)
    calll    _add
    xorl    %ecx, %ecx
    movl    %eax, -4(%ebp)
    movl    %ecx, %eax
    addl    $40, %esp
    popl    %ebp
    retl
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

x86-64 Compilation

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 14    sdk_version 10, 14
    .globl    _add                    ## -- Begin function add
    .p2align    4, 0x90
_add:                                   ## @add
    .cfi_startproc
## %bb.0:
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movl    24(%rbp), %eax
    movl    16(%rbp), %r10d
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    %edx, -12(%rbp)
    movl    %ecx, -16(%rbp)
    movl    %r8d, -20(%rbp)
    movl    %r9d, -24(%rbp)
    movl    -4(%rbp), %ecx
    addl    -8(%rbp), %ecx
    addl    -12(%rbp), %ecx
    addl    -16(%rbp), %ecx
    addl    -20(%rbp), %ecx
    addl    -24(%rbp), %ecx
    addl    16(%rbp), %ecx
    addl    24(%rbp), %ecx
    movl    %eax, -28(%rbp)         ## 4-byte Spill
    movl    %ecx, %eax
    movl    %r10d, -32(%rbp)        ## 4-byte Spill
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .globl    _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $32, %rsp
    movl    $1, %edi
    movl    $2, %esi
    movl    $3, %edx
    movl    $4, %ecx
    movl    $5, %r8d
    movl    $6, %r9d
    movl    $7, (%rsp)
    movl    $8, 8(%rsp)
    callq    _add
    xorl    %ecx, %ecx
    movl    %eax, -4(%rbp)
    movl    %ecx, %eax
    addq    $32, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

You can see that Clang's compiled X86 target code does not pass parameters using registers, whereas in X86-64 target code, the first six parameters are passed using registers.

A typical Go function call

The calling convention chosen by Go is:

  • Parameters are passed entirely through the stack, from the right to the left stack of the parameter list
  • The return value is passed through the stack, and the stack space of the return value precedes the parameter, that is, the return value is closer to the bottom of the caller stack
  • caller is responsible for cleaning the stack
package main

func main() {
    add(1,2)
}

//go:noinline
func add(a , b int) int {
    c := 3
    d := a + b + c
    return d
}
TEXT main.main(SB) /Users/user/go/src/test/main.go
  main.go:4        0x104ea20        65488b0c2530000000    MOVQ GS:0x30, CX            
  main.go:4        0x104ea29        483b6110        CMPQ 0x10(CX), SP    
  main.go:4        0x104ea2d        762e            JBE 0x104ea5d                
  main.go:4        0x104ea2f        4883ec20        SUBQ $0x20, SP ; Increase 32 bytes The stack space of(Four qword,8 individual bytes For one qword)                
  main.go:4        0x104ea33        48896c2418        MOVQ BP, 0x18(SP) ; take BP Write the value of the to the first of the stack spaces just allocated qword
  main.go:4        0x104ea38        488d6c2418        LEAQ 0x18(SP), BP ; Assign the address of the first word of the stack space just allocated to BP(That is BP Pointing at the point where you just saved the old BP Address of value)            
  main.go:5        0x104ea3d        48c7042401000000    MOVQ $0x1, 0(SP); Will give add The first argument value of the function, 1, is written to the last one that has just allocated stack space qword
  main.go:5        0x104ea45        48c744240802000000    MOVQ $0x2, 0x8(SP); Will give add The second argument value of the function, 2, is written to the third just allocated stack space qword. The second qword Not used, actually given to callee Used to store returned values.             
  main.go:5        0x104ea4e        e81d000000        CALL main.add(SB); call add function    
  main.go:6        0x104ea53        488b6c2418        MOVQ 0x18(SP), BP; Will be the fourth from the stack qword Will be old BP Value is assigned back to BP
  main.go:6        0x104ea58        4883c420        ADDQ $0x20, SP; increase SP Value, stack shrink, retract 32 bytes The stack space of                 
  main.go:6        0x104ea5c        c3            RET                    
  
  TEXT main.add(SB) /Users/user/go/src/test/main.go
  main.go:11        0x104ea70        4883ec18        SUBQ $0x18, SP; Allocation 24 bytes Of stack space (3 qword).         
  main.go:11        0x104ea74        48896c2410        MOVQ BP, 0x10(SP); take BP Value Writes First qword
  main.go:11        0x104ea79        488d6c2410        LEAQ 0x10(SP), BP; 24 that will have just been allocated bytes The address of the first word in stack space is assigned to BP(That is BP Pointing at the point where you just saved the old BP Address of value)        
  main.go:11        0x104ea7e        48c744243000000000    MOVQ $0x0, 0x30(SP);Zero address to store return value, 0 x30(SP) The corresponding memory location is the previous segment main.main Second stack space allocated in qword. 
  main.go:12        0x104ea87        48c744240803000000    MOVQ $0x3, 0x8(SP); Corresponding c := 3 This line of code.local variable c This corresponds to memory on the stack.3 is written to the 24 just assigned bytes Second in space qword. 
  main.go:13        0x104ea90        488b442420        MOVQ 0x20(SP), AX; take add Argument 1 is written to AX Register.
  main.go:13        0x104ea95        4803442428        ADDQ 0x28(SP), AX; take add Increase argument 2 to AX Register.
  main.go:13        0x104ea9a        4883c003        ADDQ $0x3, AX; Increase local variable value 3 to AX register        
  main.go:13        0x104ea9e        48890424        MOVQ AX, 0(SP); take AX Value of(Calculation results) Write to just assigned 24 bytes The third in space qword. (Correspondence Code d := a + b + c)
  main.go:14        0x104eaa2        4889442430        MOVQ AX, 0x30(SP); take AX Values written to main The stack space reserved for return values in(main 32 allocated in bytes Second in qword)
  main.go:14        0x104eaa7        488b6c2410        MOVQ 0x10(SP), BP; recovery BP The value of is the old saved at the function entry BP Value of.
  main.go:14        0x104eaac        4883c418        ADDQ $0x18, SP; take SP Add three words, retract add The stack space allocated at the entrance.    
  main.go:14        0x104eab0        c3            RET            

During a function call, the stack changes as shown in the following figure:

Initial state:

Calladd Execution Front Stack Status:

Stack state after entering add:

add Ri ret Execution Front Stack Status:

main ret Execution Front Stack Status:

You can see that the call process to Go is similar to C, except that Gos parameters are passed entirely through the stack, and Gos return values are also passed through the stack.For each data type's performance when passed as a parameter, you can test it:

How different data types are passed as parameters

Parameter Passing of Go Base Data Type

package main

import (
    "fmt"
    "runtime/debug"
)

func main() {

    str := "hello"
    int8 := int8(8)
    int64 := int64(64)

    boolValue := true

    ExampleStr(str)
    ExampleBool(boolValue)
    ExampleInt8(int8)
    ExampleInt64(int64)
    ExampleMultiParams(false, 9, 8, 7)
}


func ExampleStr(str string){
    fmt.Println(string(debug.Stack()))
}

func ExampleBool(boolValue bool){

    boolValue = false

    fmt.Println(string(debug.Stack()))
}



func ExampleInt64(v int64){
    fmt.Println(string(debug.Stack()))
}

func ExampleInt8(v int8){
    fmt.Println(string(debug.Stack()))
}

func ExampleMultiParams(b bool, x, y, z int8){
    bl := b
    xl := x
    yl := y
    zl := z

    fmt.Println(bl, xl, yl, zl)
    fmt.Println(string(debug.Stack()))

}
goroutine 1 [running]:
runtime/debug.Stack(0xc000084f38, 0x1057aad, 0x10aeb20)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleStr(0x10c6c34, 0x5)
    /Users/user/go/src/test/main.go:25 +0x26
main.main()
    /Users/user/go/src/test/main.go:16 +0x36

goroutine 1 [running]:
runtime/debug.Stack(0x10e0580, 0xc000092000, 0xc000084f58)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleBool(0x10c6c01)
    /Users/user/go/src/test/main.go:32 +0x26
main.main()
    /Users/user/go/src/test/main.go:17 +0x3f

goroutine 1 [running]:
runtime/debug.Stack(0x10e0580, 0xc000092000, 0xc000084f58)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleInt8(0x10c6c08)
    /Users/user/go/src/test/main.go:42 +0x26
main.main()
    /Users/user/go/src/test/main.go:18 +0x48

goroutine 1 [running]:
runtime/debug.Stack(0x10e0580, 0xc000092000, 0xc000084f58)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleInt64(0x40)
    /Users/user/go/src/test/main.go:38 +0x26
main.main()
    /Users/user/go/src/test/main.go:19 +0x55

false 9 8 7
goroutine 1 [running]:
runtime/debug.Stack(0x10e0580, 0xc000092000, 0xc000084f28)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleMultiParams(0x7080900)
    /Users/user/go/src/test/main.go:52 +0xf6
main.main()
    /Users/user/go/src/test/main.go:20 +0x61

You can see:

  • When string is passed, it is divided into pointer and length parameters.
  • When int64 is passed, the value is copied for transfer.
  • When looking at the call stack printed by debug.Stack(), int8 and bool passes, a memory address is passed, which seems to be misleading. Is it the memory address of the variable in the caller that is passed?Does that not result in a callee change and a caller median change?Of course not!Both int and bool are, of course, value passing.When a caller passes to a callee, both int and bool are copied into the caller's stack for use by the callee.(The parameter value is modified directly in the callee by referencing the parameter name, and the memory location of the parameter is actually on the caller's stack).
  • The ExampleMultiParams function has four parameters, but the call stack prints out and passes only a value of 0x7080900. Why?Originally these four parameters were all a byte, which together is a double word.Looking at the assembly code, you can see that the compiler has been optimized to combine directly into a single value and write the command MOVL MOVL $0x7080900, 0 (SP) x7080900, 0(SP) on the stack in the caller.Of course, when the value is taken in the caller, the value is taken byte by byte with MOVB.Of course, if these four parameters are the four local variables in main, ExampleMultiParams are called b y passing the variable name (ExampleMultiParams(b, x, y, z) instead of ExampleMultiParams(true, 9, 8, 7), which is another form of assembly code.

Parameter Passing of Go Combination Data Type

package main

import (
"fmt"
"runtime/debug"
)

type MyStruct struct {
    a int
    b string
}

func main() {
    slice := make([]string, 2, 4)
    array := [...]int{9,8,7,6,7,8,9}
    myMap := make(map[string]int)
    myStruct := MyStruct{8, "test"}
    myStructPtr := &myStruct
    myChan := make(chan int, 4)


    ExampleSlice(slice)

    ExampleArray(array)

    ExampleMap(myMap)

    ExampleStruct(myStruct)

    ExamplePtr(myStructPtr)

    ExampleChan(myChan)
}

func ExampleSlice(slice []string){
    fmt.Println(string(debug.Stack()))
}

func ExampleArray(array [7]int){
    fmt.Println(string(debug.Stack()))
}

func ExampleMap(myMap map[string]int){
    fmt.Println(string(debug.Stack()))
}

func ExampleStruct(myStruct MyStruct){
    fmt.Println(string(debug.Stack()))
}

func ExamplePtr(ptr *MyStruct){
    fmt.Println(string(debug.Stack()))
}

func ExampleChan(myChan chan int){
    fmt.Println(string(debug.Stack()))
}

Call Stack

goroutine 1 [running]:
runtime/debug.Stack(0x130a568, 0xc00007eda8, 0x1004218)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleSlice(0xc00007ee78, 0x2, 0x4)
    /Users/user/go/src/test/main.go:62 +0x26
main.main()
    /Users/user/go/src/test/main.go:46 +0x159

goroutine 1 [running]:
runtime/debug.Stack(0x10e0a80, 0xc00008c000, 0xc00007ed98)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleArray(0x9, 0x8, 0x7, 0x6, 0x7, 0x8, 0x9)
    /Users/user/go/src/test/main.go:66 +0x26
main.main()
    /Users/user/go/src/test/main.go:48 +0x185

goroutine 1 [running]:
runtime/debug.Stack(0x10e0a80, 0xc00008c000, 0xc00007ed98)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleMap(0xc00007ee48)
    /Users/user/go/src/test/main.go:74 +0x26
main.main()
    /Users/user/go/src/test/main.go:52 +0x1af

goroutine 1 [running]:
runtime/debug.Stack(0x10e0a80, 0xc00008c000, 0xc00007ed98)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleStruct(0x8, 0x10c6f88, 0x4)
    /Users/user/go/src/test/main.go:78 +0x26
main.main()
    /Users/user/go/src/test/main.go:54 +0x1d7

goroutine 1 [running]:
runtime/debug.Stack(0x10e0a80, 0xc00008c000, 0xc00007ed98)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExamplePtr(0xc00007ee30)
    /Users/user/go/src/test/main.go:82 +0x26
main.main()
    /Users/user/go/src/test/main.go:56 +0x1e5

goroutine 1 [running]:
runtime/debug.Stack(0x10e0a80, 0xc00008c000, 0xc00007ed98)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.ExampleChan(0xc000092000)
    /Users/user/go/src/test/main.go:86 +0x26
main.main()
    /Users/user/go/src/test/main.go:58 +0x1f3

You can see:

  • For arrays, structs, and pointers, a copy is passed to callee.
  • When an array is a parameter, the number of compiled parameters is the number of array elements.
  • When a structure is used as a parameter, the number of compiled parameters requires a reanalysis of the type of elements in the structure.As in the above code, the structure consists of an int and a string, and the parameters passed are the int value, the address of the string, and the length of the string.
  • When slice is passed, the pointer, len, cap at the bottom of the slice are passed as three parameters separately.(That is, after compilation, the number of parameters changes from one parameter in the source code to three parameters).So slice is actually value transfer.
  • When map and chan are passed, the address pointers of map and chan are passed as parameters.

Method: pointer receiver and value receiver

In Java, when we call an object's method, we can certainly modify its member variables.In Go, however, the result depends on whether the recipient of the method is a value receiver or pointer receiver when defining the method.

Go s have two method receivers: value receiver and pointer receiver.Value Recipient, the type of Recipient is a value, a copy, and there is no way to change its true recipient internally.Pointer Receiver, the type of recipient is a pointer and is a reference to the recipient. Modifications to this reference will affect the real recipient.

See the following code:

package main

import "fmt"

type XAxis int

type Point struct{
    X int
    Y int
}


func (x XAxis)VIncr(offset XAxis){
    x += offset
    fmt.Printf("In VIncr, new x = %d\n", x)
}

func (x *XAxis)PIncr(offset XAxis){
    *x += offset
    fmt.Printf("In PIncr, new x = %d\n", *x)
}

func (p Point)VScale(factor int){
    p.X *= factor
    p.Y *= factor

    fmt.Printf("In VScale, new p = %v\n", p)
}


func (p *Point)PScale(factor int){
    p.X *= factor
    p.Y *= factor

    fmt.Printf("In PScale, new p = %v\n", p)
}

func main(){
    var x XAxis = 10

    fmt.Printf("In main, before VIncr, x = %v\n", x)
    x.VIncr(5)
    fmt.Printf("In main, after VIncr, new x = %v\n", x)

    fmt.Println()

    fmt.Printf("In main, before PIncr, x = %v\n", x)
    x.PIncr(5)
    fmt.Printf("In main, after PIncr, new x = %v\n", x)

    fmt.Println()

    p := Point{2, 2}

    fmt.Printf("In main, before VScale, p = %v\n", p)
    p.VScale(5)
    fmt.Printf("In main, after VScale, new p = %v\n", p)

    fmt.Println()

    fmt.Printf("In main, before PScale, p = %v\n", p)
    p.PScale(5)
    fmt.Printf("In main, after PScale, new p = %v\n", p)
}

Output:

In main, before VIncr, x = 10
In VIncr, new x = 15
In main, after VIncr, new x = 10

In main, before PIncr, x = 10
In PIncr, new x = 15
In main, after PIncr, new x = 15

In main, before VScale, p = {2 2}
In VScale, new p = {10 10}
In main, after VScale, new p = {2 2}

In main, before PScale, p = {2 2}
In PScale, new p = &{10 10}
In main, after PScale, new p = {10 10}

When defining a method, the receiver precedes the method name, not in the parameter list. How do the instructions in the method find the receiver when the method executes?
Let's simplify the code (comment out the print-related statements) and decompile it:

TEXT %22%22.XAxis.VIncr(SB) gofile../Users/user/go/src/test/main.go
  main.go:14        0xbb0            488b442408        MOVQ 0x8(SP), AX    
  main.go:14        0xbb5            4803442410        ADDQ 0x10(SP), AX    
  main.go:14        0xbba            4889442408        MOVQ AX, 0x8(SP)    
  main.go:16        0xbbf            c3            RET            

TEXT %22%22.(*XAxis).PIncr(SB) gofile../Users/user/go/src/test/main.go
  main.go:19        0xbd4            488b442408        MOVQ 0x8(SP), AX    
  main.go:19        0xbd9            8400            TESTB AL, 0(AX)        
  main.go:19        0xbdb            488b4c2408        MOVQ 0x8(SP), CX    
  main.go:19        0xbe0            8401            TESTB AL, 0(CX)        
  main.go:19        0xbe2            488b00            MOVQ 0(AX), AX        
  main.go:19        0xbe5            4803442410        ADDQ 0x10(SP), AX    
  main.go:19        0xbea            488901            MOVQ AX, 0(CX)        
  main.go:21        0xbed            c3            RET            

TEXT %22%22.Point.VScale(SB) gofile../Users/user/go/src/test/main.go
  main.go:24        0xc0a            488b442408        MOVQ 0x8(SP), AX    
  main.go:24        0xc0f            488b4c2418        MOVQ 0x18(SP), CX    
  main.go:24        0xc14            480fafc1        IMULQ CX, AX        
  main.go:24        0xc18            4889442408        MOVQ AX, 0x8(SP)    
  main.go:25        0xc1d            488b442410        MOVQ 0x10(SP), AX    
  main.go:25        0xc22            488b4c2418        MOVQ 0x18(SP), CX    
  main.go:25        0xc27            480fafc1        IMULQ CX, AX        
  main.go:25        0xc2b            4889442410        MOVQ AX, 0x10(SP)    
  main.go:28        0xc30            c3            RET            

TEXT %22%22.(*Point).PScale(SB) gofile../Users/user/go/src/test/main.go
  main.go:32        0xc47            488b442408        MOVQ 0x8(SP), AX    
  main.go:32        0xc4c            8400            TESTB AL, 0(AX)        
  main.go:32        0xc4e            488b4c2408        MOVQ 0x8(SP), CX    
  main.go:32        0xc53            8401            TESTB AL, 0(CX)        
  main.go:32        0xc55            488b00            MOVQ 0(AX), AX        
  main.go:32        0xc58            488b542410        MOVQ 0x10(SP), DX    
  main.go:32        0xc5d            480fafc2        IMULQ DX, AX        
  main.go:32        0xc61            488901            MOVQ AX, 0(CX)        
  main.go:33        0xc64            488b442408        MOVQ 0x8(SP), AX    
  main.go:33        0xc69            8400            TESTB AL, 0(AX)        
  main.go:33        0xc6b            488b4c2408        MOVQ 0x8(SP), CX    
  main.go:33        0xc70            8401            TESTB AL, 0(CX)        
  main.go:33        0xc72            488b4008        MOVQ 0x8(AX), AX    
  main.go:33        0xc76            488b542410        MOVQ 0x10(SP), DX    
  main.go:33        0xc7b            480fafc2        IMULQ DX, AX        
  main.go:33        0xc7f            48894108        MOVQ AX, 0x8(CX)    
  main.go:36        0xc83            c3            RET            

TEXT %22%22.main(SB) gofile../Users/user/go/src/test/main.go
  main.go:38        0xcaa            65488b0c2500000000    MOVQ GS:0, CX        [5:9]R_TLS_LE        
  main.go:38        0xcb3            483b6110        CMPQ 0x10(CX), SP    
  main.go:38        0xcb7            0f86b3000000        JBE 0xd70        
  main.go:38        0xcbd            4883ec50        SUBQ $0x50, SP        
  main.go:38        0xcc1            48896c2448        MOVQ BP, 0x48(SP)    
  main.go:38        0xcc6            488d6c2448        LEAQ 0x48(SP), BP    
  main.go:39        0xccb            48c74424300a000000    MOVQ $0xa, 0x30(SP)    
  main.go:42        0xcd4            48c704240a000000    MOVQ $0xa, 0(SP)    
  main.go:42        0xcdc            48c744240805000000    MOVQ $0x5, 0x8(SP)    
  main.go:42        0xce5            e800000000        CALL 0xcea        [1:5]R_CALL:%22%22.XAxis.VIncr    
  main.go:48        0xcea            488d442430        LEAQ 0x30(SP), AX    
  main.go:48        0xcef            48890424        MOVQ AX, 0(SP)        
  main.go:48        0xcf3            48c744240805000000    MOVQ $0x5, 0x8(SP)    
  main.go:48        0xcfc            e800000000        CALL 0xd01        [1:5]R_CALL:%22%22.(*XAxis).PIncr    
  main.go:53        0xd01            0f57c0            XORPS X0, X0        
  main.go:53        0xd04            0f11442438        MOVUPS X0, 0x38(SP)    
  main.go:53        0xd09            48c744243802000000    MOVQ $0x2, 0x38(SP)    
  main.go:53        0xd12            48c744244002000000    MOVQ $0x2, 0x40(SP)    
  main.go:56        0xd1b            48c7042402000000    MOVQ $0x2, 0(SP)    
  main.go:56        0xd23            48c744240802000000    MOVQ $0x2, 0x8(SP)    
  main.go:56        0xd2c            48c744241005000000    MOVQ $0x5, 0x10(SP)    
  main.go:56        0xd35            e800000000        CALL 0xd3a        [1:5]R_CALL:%22%22.Point.VScale    
  main.go:62        0xd3a            488d442438        LEAQ 0x38(SP), AX    
  main.go:62        0xd3f            48890424        MOVQ AX, 0(SP)        
  main.go:62        0xd43            48c744240805000000    MOVQ $0x5, 0x8(SP)    
  main.go:62        0xd4c            e800000000        CALL 0xd51        [1:5]R_CALL:%22%22.(*Point).PScale    
  main.go:64        0xd51            48c7042400000000    MOVQ $0x0, 0(SP)    
  main.go:64        0xd59            0f57c0            XORPS X0, X0        
  main.go:64        0xd5c            0f11442408        MOVUPS X0, 0x8(SP)    
  main.go:64        0xd61            e800000000        CALL 0xd66        [1:5]R_CALL:fmt.Println    
  main.go:65        0xd66            488b6c2448        MOVQ 0x48(SP), BP    
  main.go:65        0xd6b            4883c450        ADDQ $0x50, SP        
  main.go:65        0xd6f            c3            RET            
  main.go:38        0xd70            e800000000        CALL 0xd75        [1:5]R_CALL:runtime.morestack_noctxt    
  main.go:38        0xd75            e930ffffff        JMP %22%22.main(SB)    

For example, in main.go line 42 x.VIncr(5), the instructions for passing parameters are

  main.go:42        0xcd4            48c704240a000000    MOVQ $0xa, 0(SP); Make value 10 the first parameter
  main.go:42        0xcdc            48c744240805000000    MOVQ $0x5, 0x8(SP); Value 5 as second parameter    
  main.go:42        0xce5            e800000000        CALL 0xcea        [1:5]R_CALL:%22%22.XAxis.VIncr    

Look again at the assembly code for x.VIncr:

  main.go:14        0xbb0            488b442408        MOVQ 0x8(SP), AX; Take the first parameter to the register AX    
  main.go:14        0xbb5            4803442410        ADDQ 0x10(SP), AX; Add the second parameter to the register AX
  main.go:14        0xbba            4889442408        MOVQ AX, 0x8(SP); Writes and to the position of the first parameter on the stack
  main.go:16        0xbbf            c3            RET    

You can see that the receiver of method VIncr is a value, then caller copies this value to the parameter area of VIncr on the stack when calling VIncr. VIncr's modification of the receiver is actually the value in the parameter area modified, not the value of the receiver in the area where the local variable is stored in the caller stack (in language)The user's perspective is to modify the copy instead of the original value.

Next, let's look at line 48 x.PIncr(5) of main.go, where the instruction invoked is:

  main.go:39        0xccb            48c74424300a000000    MOVQ $0xa, 0x30(SP)    
...
  main.go:48        0xcea            488d442430        LEAQ 0x30(SP), AX; take SP+0x30 This memory address is saved to AX;    SP+0x30 10 is stored in this memory address(implement var x XAxis = 10 Local variable defined by time)
  main.go:48        0xcef            48890424        MOVQ AX, 0(SP); take AX The value of the is used as the first parameter.        
  main.go:48        0xcf3            48c744240805000000    MOVQ $0x5, 0x8(SP); Make 5 the second parameter    
  main.go:48        0xcfc            e800000000        CALL 0xd01        [1:5]R_CALL:%22%22.(*XAxis).PIncr

Assembly code for PIncr:

  main.go:19        0xbd4            488b442408        MOVQ 0x8(SP), AX; The first parameter will be(That is main Local variable on frame stack of x Memory Address)Read To AX    
  main.go:19        0xbd9            8400            TESTB AL, 0(AX)        
  main.go:19        0xbdb            488b4c2408        MOVQ 0x8(SP), CX; The first parameter will be(That is main Local variable on frame stack of x Memory Address)Read To CX    
  main.go:19        0xbe0            8401            TESTB AL, 0(CX)        
  main.go:19        0xbe2            488b00            MOVQ 0(AX), AX; from AX Read the memory address, get the value from the memory address, and then read AX(Namely main Local variable x Value of)        
  main.go:19        0xbe5            4803442410        ADDQ 0x10(SP), AX; Add the second parameter 5 to AX in
  main.go:19        0xbea            488901            MOVQ AX, 0(CX); Write calculation results to CX Memory address in(That is main Local variable on frame stack of x Memory Address)    
  main.go:21        0xbed            c3            RET    

You can see that the receiver of the method PIncr is a pointer, and when the caller calls PIncr, it copies the pointer into the parameter area on the stack to the PIncr. When the PIncr modifies the receiver, it actually modifies the memory area that the pointer points to, that is, the local variable x of the main.(From the language user's perspective, the original value is modified).

Print the call stack to see the parameters actually passed more easily than assembly:

goroutine 1 [running]:
runtime/debug.Stack(0x1036126, 0x10a4360, 0xc000098000)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.XAxis.VIncr(0xa, 0x5)
    /Users/user/go/src/test/main.go:18 +0x26
main.main()
    /Users/user/go/src/test/main.go:47 +0x40

goroutine 1 [running]:
runtime/debug.Stack(0x10e0480, 0xc000094000, 0xc000080f10)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.(*XAxis).PIncr(0xc000080f70, 0x5)
    /Users/user/go/src/test/main.go:24 +0x33
main.main()
    /Users/user/go/src/test/main.go:53 +0x57

goroutine 1 [running]:
runtime/debug.Stack(0x10e0480, 0xc000094000, 0xc000080f10)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.Point.VScale(0x2, 0x2, 0x5)
    /Users/user/go/src/test/main.go:31 +0x26
main.main()
    /Users/user/go/src/test/main.go:61 +0x90

goroutine 1 [running]:
runtime/debug.Stack(0x10e0480, 0xc000094000, 0xc000080f10)
    /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
main.(*Point).PScale(0xc000080f78, 0x5)
    /Users/user/go/src/test/main.go:39 +0x46
main.main()
    /Users/user/go/src/test/main.go:67 +0xa7

When the receiver of a method is a value, a copy of the value is passed to the callee as the first parameter when the method is called, so that the modification to the receiver in the caller is actually a copy of the modification, without affecting the original value.When the receiver of a method is a pointer, a copy of the pointer is passed to the caller as the first parameter so that the caller can modify the original value through the pointer.

A little supplement

  • If modifying the value of the receiver in a method is to take effect on the caller, use pointer receiver
  • For performance optimization, if a receiver is a data type that takes up more memory, such as a structure or an array, use pointer receiver first

Be careful:

  • Value sinks are concurrently secure, while pointer sinks are not concurrently secure.

Invocation rules:

  • Callable method set of type T contains all method sets whose recipient is T or T
  • The callable method set of type T contains all methods whose recipient is T
  • The set of callable methods of type T does not contain methods whose recipient is *T

Method Receiver vs. Function/Method Parameters:

  • The argument type and the parameter type of the function/method must be the same (syntactically) not one pointer and the other value.
  • The recipient of the method is smart and can be called on a value if it is a pointer receiver (the compiler automatically inserts a pointer-to-value instruction).If it is a value receiver, the compiler automatically converts the pointer to the value corresponding to the pointer when the method is called on the pointer.

Anonymous functions and closures

Anonymous function

An anonymous function consists of a function declaration and a body without a function name, which can be assigned to a variable, passed as a struct field, or in a channel.In the underlying implementation, what is actually passed is the entry address of the anonymous function.

package main

func test() func(int) int {
    return func(x int) int {
        x += x
        return x
    }
}

func main() {

    f := test()
    f(100)
}
TEXT %22%22.test(SB) gofile../Users/user/go/src/test/main.go
  main.go:4        0x4e7            48c744240800000000    MOVQ $0x0, 0x8(SP)    
  main.go:5        0x4f0            488d0500000000        LEAQ 0(IP), AX        [3:7]R_PCREL:%22%22.test.func1·f    
  main.go:5        0x4f7            4889442408        MOVQ AX, 0x8(SP)    
  main.go:5        0x4fc            c3            RET            

TEXT %22%22.main(SB) gofile../Users/user/go/src/test/main.go
  main.go:11        0x517            65488b0c2500000000    MOVQ GS:0, CX        [5:9]R_TLS_LE        
  main.go:11        0x520            483b6110        CMPQ 0x10(CX), SP    
  main.go:11        0x524            7633            JBE 0x559        
  main.go:11        0x526            4883ec20        SUBQ $0x20, SP        
  main.go:11        0x52a            48896c2418        MOVQ BP, 0x18(SP)    
  main.go:11        0x52f            488d6c2418        LEAQ 0x18(SP), BP    
  main.go:12        0x534            e800000000        CALL 0x539        [1:5]R_CALL:%22%22.test    
  main.go:12        0x539            488b1424        MOVQ 0(SP), DX        
  main.go:12        0x53d            4889542410        MOVQ DX, 0x10(SP)    
  main.go:13        0x542            48c7042464000000    MOVQ $0x64, 0(SP)    
  main.go:13        0x54a            488b02            MOVQ 0(DX), AX        
  main.go:13        0x54d            ffd0            CALL AX            [0:0]R_CALLIND        
  main.go:14        0x54f            488b6c2418        MOVQ 0x18(SP), BP    
  main.go:14        0x554            4883c420        ADDQ $0x20, SP        
  main.go:14        0x558            c3            RET            
  main.go:11        0x559            e800000000        CALL 0x55e        [1:5]R_CALL:runtime.morestack_noctxt    
  main.go:11        0x55e            ebb7            JMP %22%22.main(SB)    

TEXT %22%22.test.func1(SB) gofile../Users/user/go/src/test/main.go
  main.go:5        0x58a            48c744241000000000    MOVQ $0x0, 0x10(SP)    
  main.go:6        0x593            488b442408        MOVQ 0x8(SP), AX    
  main.go:6        0x598            4803442408        ADDQ 0x8(SP), AX    
  main.go:6        0x59d            4889442408        MOVQ AX, 0x8(SP)    
  main.go:7        0x5a2            4889442410        MOVQ AX, 0x10(SP)

closure

When a function references a variable in an external scope, we call it a closure.In the underlying implementation, a closure consists of the address of a function and the address of the variable referenced, and is stored in a structure where the address is actually passed when the closure is passed.Because the values on the stack frame fail after the frame's function exits, variables from the external scope referenced by the closure are assigned to the heap.In the following implementation, the test() function returns a closure assignment to f, which is actually the address of the closure structure (on the heap) received in the main and saved on the DX register. The memory value corresponding to the address is the address of the closure function (the function address is taken to the register and then called via call), offset by 8 bytes (+8bytes) Is the address of the variable x. When closing function f is called in main, the address of the variable x is still obtained by reading the value of DX.That is, the main call f shares the value of a register DX, although it does not pass parameters or return values.

package main

func test() func() {
    x := 100
    return func() {
        x += 100
    }
}

func main() {
    f := test()
    f()
    f()
    f()
}
TEXT %22%22.test(SB) gofile../Users/user/go/src/test/main.go
  main.go:3        0x6ad            65488b0c2500000000    MOVQ GS:0, CX        [5:9]R_TLS_LE        
  main.go:3        0x6b6            483b6110        CMPQ 0x10(CX), SP    
  main.go:3        0x6ba            0f869b000000        JBE 0x75b        
  main.go:3        0x6c0            4883ec28        SUBQ $0x28, SP        
  main.go:3        0x6c4            48896c2420        MOVQ BP, 0x20(SP)    
  main.go:3        0x6c9            488d6c2420        LEAQ 0x20(SP), BP    
  main.go:3        0x6ce            48c744243000000000    MOVQ $0x0, 0x30(SP)    
  main.go:4        0x6d7            488d0500000000        LEAQ 0(IP), AX        [3:7]R_PCREL:type.int    
  main.go:4        0x6de            48890424        MOVQ AX, 0(SP)        
  main.go:4        0x6e2            e800000000        CALL 0x6e7        [1:5]R_CALL:runtime.newobject    
  main.go:4        0x6e7            488b442408        MOVQ 0x8(SP), AX    
  main.go:4        0x6ec            4889442418        MOVQ AX, 0x18(SP)    
  main.go:4        0x6f1            48c70064000000        MOVQ $0x64, 0(AX)    
  main.go:5        0x6f8            488d0500000000        LEAQ 0(IP), AX        [3:7]R_PCREL:type.noalg.struct { F uintptr; %22%22.x *int }    
  main.go:5        0x6ff            48890424        MOVQ AX, 0(SP)        
  main.go:5        0x703            e800000000        CALL 0x708        [1:5]R_CALL:runtime.newobject    
  main.go:5        0x708            488b442408        MOVQ 0x8(SP), AX    
  main.go:5        0x70d            4889442410        MOVQ AX, 0x10(SP)    
  main.go:5        0x712            488d0d00000000        LEAQ 0(IP), CX        [3:7]R_PCREL:%22%22.test.func1    
  main.go:5        0x719            488908            MOVQ CX, 0(AX)        
  main.go:5        0x71c            488b442410        MOVQ 0x10(SP), AX    
  main.go:5        0x721            8400            TESTB AL, 0(AX)        
  main.go:5        0x723            488b4c2418        MOVQ 0x18(SP), CX    
  main.go:5        0x728            488d7808        LEAQ 0x8(AX), DI    
  main.go:5        0x72c            833d0000000000        CMPL $0x0, 0(IP)    [2:6]R_PCREL:runtime.writeBarrier+-1    
  main.go:5        0x733            7402            JE 0x737        
  main.go:5        0x735            eb1a            JMP 0x751        
  main.go:5        0x737            48894808        MOVQ CX, 0x8(AX)    
  main.go:5        0x73b            eb00            JMP 0x73d        
  main.go:5        0x73d            488b442410        MOVQ 0x10(SP), AX    
  main.go:5        0x742            4889442430        MOVQ AX, 0x30(SP)    
  main.go:5        0x747            488b6c2420        MOVQ 0x20(SP), BP    
  main.go:5        0x74c            4883c428        ADDQ $0x28, SP        
  main.go:5        0x750            c3            RET            
  main.go:5        0x751            4889c8            MOVQ CX, AX        
  main.go:5        0x754            e800000000        CALL 0x759        [1:5]R_CALL:runtime.gcWriteBarrier    
  main.go:5        0x759            ebe2            JMP 0x73d        
  main.go:3        0x75b            e800000000        CALL 0x760        [1:5]R_CALL:runtime.morestack_noctxt    
  main.go:3        0x760            e948ffffff        JMP %22%22.test(SB)    

TEXT %22%22.main(SB) gofile../Users/user/go/src/test/main.go
  main.go:10        0x7bc            65488b0c2500000000    MOVQ GS:0, CX        [5:9]R_TLS_LE        
  main.go:10        0x7c5            483b6110        CMPQ 0x10(CX), SP    
  main.go:10        0x7c9            763f            JBE 0x80a        
  main.go:10        0x7cb            4883ec18        SUBQ $0x18, SP        
  main.go:10        0x7cf            48896c2410        MOVQ BP, 0x10(SP)    
  main.go:10        0x7d4            488d6c2410        LEAQ 0x10(SP), BP    
  main.go:11        0x7d9            e800000000        CALL 0x7de        [1:5]R_CALL:%22%22.test    
  main.go:11        0x7de            488b1424        MOVQ 0(SP), DX        
  main.go:11        0x7e2            4889542408        MOVQ DX, 0x8(SP)    
  main.go:12        0x7e7            488b02            MOVQ 0(DX), AX        
  main.go:12        0x7ea            ffd0            CALL AX            [0:0]R_CALLIND        
  main.go:13        0x7ec            488b542408        MOVQ 0x8(SP), DX    
  main.go:13        0x7f1            488b02            MOVQ 0(DX), AX        
  main.go:13        0x7f4            ffd0            CALL AX            [0:0]R_CALLIND        
  main.go:14        0x7f6            488b542408        MOVQ 0x8(SP), DX    
  main.go:14        0x7fb            488b02            MOVQ 0(DX), AX        
  main.go:14        0x7fe            ffd0            CALL AX            [0:0]R_CALLIND        
  main.go:15        0x800            488b6c2410        MOVQ 0x10(SP), BP    
  main.go:15        0x805            4883c418        ADDQ $0x18, SP        
  main.go:15        0x809            c3            RET            
  main.go:10        0x80a            e800000000        CALL 0x80f        [1:5]R_CALL:runtime.morestack_noctxt    
  main.go:10        0x80f            ebab            JMP %22%22.main(SB)    

TEXT %22%22.test.func1(SB) gofile../Users/user/go/src/test/main.go
  main.go:5        0x84b            4883ec10        SUBQ $0x10, SP        
  main.go:5        0x84f            48896c2408        MOVQ BP, 0x8(SP)    
  main.go:5        0x854            488d6c2408        LEAQ 0x8(SP), BP    
  main.go:5        0x859            488b4208        MOVQ 0x8(DX), AX    
  main.go:5        0x85d            48890424        MOVQ AX, 0(SP)        
  main.go:6        0x861            48830064        ADDQ $0x64, 0(AX)    
  main.go:7        0x865            488b6c2408        MOVQ 0x8(SP), BP    
  main.go:7        0x86a            4883c410        ADDQ $0x10, SP        
  main.go:7        0x86e            c3            RET        

Recursive function

Recursive functions are essentially no different from ordinary functions, just calling themselves and increasing stacks.Stack overflow is a concern because the stack size is fixed in C.However, the Go internal stack automatically expands as needed, so there is no need to worry about this.

About defer statements

defer and return

You can try to infer the return value of a function by looking at the following three functions first:

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

func f() (r int) {
     t := 5 
     defer func() {
       t = t + 5
     }()
     return t
}

func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

The correct answers are: 1, 5, 1.If your answer is correct, you can skip the following explanation:

The phrase "the function call after defer executes before the return statement" is not easy to understand correctly.In fact, the return xxx statement is not an atom. Instead, it writes the xxx to the stack space allocated by the caller for the return value, followed by the RET instruction.The defer function is inserted and executed before the RET instruction.

The control structure of the goroutine contains a table that records defer expressions, and the compiler inserts the instruction call runtime.deferproc where defer appears, which records defer expressions in the table.The expression is then executed from the defer table on the stack before the function returns, and the instruction inserted is call runtime.deferreturn.

defer and closure

The parameters of the function invoked by the defer statement are evaluated or copied at the time of defer registration.Therefore, after a function statement passed to defer as a parameter, subsequent modifications to the local variable will no longer affect the use of the value of the variable within the defer function.However, a variable in the defer function that uses an external function passed in as a non-parameter will use the final value of the variable in the life cycle of the external function.

package main

import "fmt"


func test() {
    x, y := 10, 20
    defer func(i int) {
        fmt.Println("defer:", i, y)
    }(x)

    x += 10
    y += 100
    fmt.Println(x, y)
}

func main(){
    test()
}
Output:
20 120
defer: 10 120

Remarks:

  • The stack in memory grows from high address space to low address space, the top of the stack is smaller than the bottom of the stack, and allocating stack space corresponds to a decrease in sp value.
  • Write values are written from low addresses to high addresses, such as SP pointing to 0xff00 and writing a word (8 bytes) into the stack, taking up 8 bytes from 0xff00 to 0xff07.
  • The order of intel storage bytes is small-end first: that is, low-valid bytes are stored in low-memory addresses.
  • In IA-32 and X86-64, word length is defined as 16 bits, dword as 32 bits, and qword as 64 bits.

Methods for generating assembly files

  1. Generate using go tool compile-N-l-S go_file.go
  2. Use go tool compile -N-l go_file.go to compile into a binary file, then execute go tool objdump bin_name.o to disassemble the code, specifying the function name by -s to disassemble only specific functions: go tool objdump-s YOUR_FUNC_NAME bin_name.o
  3. Generate using go build-gcflags-S

Note: go tool compile and go build-gcflags-S produce assembly in process, go tool objdump produce assembly of final machine code.

summary

The Go language uses the stack to pass parameters and return values, and the caller is responsible for the stack. Passing return values through the stack enables the Go function to support multiple return values, while the caller stack enables functions with variable parameters.Gos pass parameters using value-passing mode, so when passing arrays and structures, you should try to use pointers as parameters to avoid large copies of data and improve performance.
The Go method is called by passing the receiver as an argument to the callee, the receiver score receiver, and the pointer receiver.
When passing an anonymous function, the pass is actually the function's entry pointer.When closures are used, Go s allocates variables to heap memory through an escape analysis mechanism, and variable addresses and function entry addresses form a structure on the heap that is passed when closures are passed.
Gos data types are divided into value and reference types, but Gos parameter transfer is value transfer.When a value type is passed, it is a complete copy, and changes to parameters in the callee do not affect the original value; when a reference type is passed, changes in the callee affect the original value.
A return statement with a return value corresponds to multiple machine instructions, starting with writing the return value to the space allocated by the caller on the stack for the return value, and then executing the ret command.When there is a defer statement, the function in the defer statement is executed before the ret instruction is inserted.

Reference and in-depth reading:

About invocation conventions
x86 calling conventions
Calling convention

About memory layout and function frame stack
Function Call and Stack Frame Principle under x86-64
Process Memory Layout
Anatomy of a Program in Memory
Linux Process Address Space &&Process Memory Layout
Runtime memory layout and stack frame structure
Memory Layout for C programs
C Function Call Procedure Principle and Function Stack Frame Analysis

About Plan9 Assemblies
Getting started with plan9 assembly
golang assembly Basics
golang assembly

About Go
Function declarations, Method declarations
Effective Go Functions
Delve into the goroutine stack
Go defer keyword
Go Language Advanced Programming Re-discussion Functions

This article references code samples and statements from these articles

Stack Traces In Go
Talking about the Implementation Principle of Go Language-Function Call

Posted by Tainted Kitten on Sat, 13 Jul 2019 10:50:38 -0700