-10 +

go calling convention

这篇文章翻译自 dr knz @ work go 调用规约,原文出处在文章末尾,作者有两篇文章。 由于最近需要对各种语言对内存布局感兴趣,所以打算先研究 go 语言的调用规约,翻译可能存在错误,欢迎斧正。

Introduction

Calling convention

Arguments and return value

Call sequence: how a function gets called

在 go 1.10 和 1.15 版本中, 一个函数为被调用函数设置好参数,并且在栈上预留出返回值的空间,被调用函数在返回值写入预留的空间中。

在以前一样,这样设计的一个副作用是,当函数返回一个

As before, a side effect of this design is that when a function returns the same value as one of its callees, it needs to read the return value from the callee from its own activation record, then place it back onto the stack at a return value in its caller’s activation record. Tail call optimizations (TCO) thus remain impossible.

Additionally, function prologues remain largely unchanged:

通产,结束不会做这些操作。

这里又一个例子来自 go 运行时的内部函数,来演示函数序言和结束:

internal/cpu.Initialize:
        ; Check remaining stack size:
        MOVQ FS:0xfffffff8, CX
        CMPQ 0x10(CX), SP ; at least 24 bytes on the stack?
        JBE 0x401047      ; no: go to block at end of function below

        ; Allocate activation record:
        SUBQ $0x18, SP    ; 24 bytes in activation record

        ; Set up the frame pointer
        MOVQ BP, 0x10(SP) ; BP is callee-save: store it
        LEAQ 0x10(SP), BP ; set up new frame pointer
        ...

        MOVQ 0x10(SP), BP ; restore the caller's frame pointer
        ADDQ $0x18, SP    ; deallocate the activation record
        RET               ; return

0x401047:
        CALL runtime.morestack_noctxt(SB) ; alloc more stack
        JMP internal/cpu.Initialize(SB) ; restart

Callee-save registers—or not

The cost of pointers and interfaces

// Define a struct type implementing the interface by value.
type bar struct{ x int }
func (bar) foo() {}
// Define a global variable so we don't use the heap allocator.
var y bar

// Make an interface value.
func MakeInterface2() Foo { return y }
MakeInterface2:
   ; <function prologue>

   ; write y to 0(SP), as an argument to runtime.convT64
   0x45c55d                488b057cc70900          MOVQ main.y(SB), AX
   0x45c564                48890424                MOVQ AX, 0(SP)
   ; call runtime.convT64, this converts the object to a heap reference
   0x45c568                e833c5faff              CALL runtime.convT64(SB)
   ; extract the return value
   0x45c56d                488b442408              MOVQ 0x8(SP), AX
   ; take the vtable pointer
   0x45c572                488d0d07c00200          LEAQ go.itab.main.bar,main.Foo(SB), CX
   ; write both to the return value slot for MakeInterface2
   0x45c579                48894c2420              MOVQ CX, 0x20(SP)
   0x45c57e                4889442428              MOVQ AX, 0x28(SP)

   ; <function epilogue>
   0x45c58c                c3                      RET
MakeInterface2:
   ; <function prologue>

   ; take the vtable pointer
   0x4805dd              488d053c020400          LEAQ go.itab.src.bar,src.Foo(SB), AX
   ; pass it as argument to convT2I64
   0x4805e4              48890424                MOVQ AX, 0(SP)
   ; take the address of y
   0x4805e8              488d05e9f10b00          LEAQ main.y(SB), AX
   ; pass it as argument to convT2I64
   0x4805ef              4889442408              MOVQ AX, 0x8(SP)
   ; convert to interface reference
   0x4805f4              e8e7b2f8ff              CALL runtime.convT2I64(SB)
   ; copy the return value from runtime.convT2I64 to the return slot of MakeInterface2
   0x4805f9              488b442410              MOVQ 0x10(SP), AX
   0x4805fe              488b4c2418              MOVQ 0x18(SP), CX
   0x480603              4889442430              MOVQ AX, 0x30(SP)
   0x480608              48894c2438              MOVQ CX, 0x38(SP)

   ; <function epilogue>
   0x480616              c3                      RET

Vararg calls

func f(...int) {}

var x,y,z,w int
func caller() {
   f(x,y,z,w)
}
caller:
    ; <function prologue>

    ; fill the slice:
    XORPS X0, X0          ; set 2 words (128 bit) to zero in X0
    MOVUPS X0, 0x18(SP)   ; initialize the 4-element slice to zero
    MOVUPS X0, 0x28(SP)   ; initialize the 4-element slice to zero
    MOVQ main.x(SB), AX
    MOVQ AX, 0x18(SP)     ; store x into 1st position
    MOVQ main.y(SB), AX
    MOVQ AX, 0x20(SP)     ; store y into 2nd position
    MOVQ main.z(SB), AX
    MOVQ AX, 0x28(SP)     ; store z into 3rd position
    MOVQ main.w(SB), AX
    MOVQ AX, 0x30(SP)     ; store w into 4th position

    ; prepare the slice as outgoing argument:
    LEAQ 0x18(SP), AX     ; store the base address
    MOVQ AX, 0(SP)
    MOVQ $0x4, 0x8(SP)    ; store the length
    MOVQ $0x4, 0x10(SP)   ; store the capacity

    CALL main.g(SB)       ; call the function

    ; <function epilogue>
    RET
// note: now we have an interface type.
func f(...interface{}) {}

var x,y,z,w int
func caller() {
   f(x,y,z,w)
}
caller:
    ; <function prologue>

    ; fill the slice:
    XORPS X0, X0         ; zero out the slice
    MOVUPS X0, 0x38(SP)
    MOVUPS X0, 0x48(SP)
    MOVUPS X0, 0x58(SP)
    MOVUPS X0, 0x68(SP)

    MOVQ main.x(SB), AX
    MOVQ AX, 0x30(SP)    ; copy x on the stack, out of the slice
    LEAQ 0x7995(IP), AX
    MOVQ AX, 0x38(SP)    ; place x's interface{} vtable ptr in the slice
    LEAQ 0x30(SP), CX
    MOVQ CX, 0x40(SP)    ; place the address of x's copy in the slice

    MOVQ main.y(SB), CX
    MOVQ CX, 0x28(SP)    ; copy y on the stack, out of the slice
    MOVQ AX, 0x48(SP)    ; place the same vtable ptr as x in the slice
    LEAQ 0x28(SP), CX
    MOVQ CX, 0x50(SP)    ; place the address of y's copy in the slice

    MOVQ main.z(SB), CX
    MOVQ CX, 0x20(SP)    ; copy z on the stack, out of the slice
    MOVQ AX, 0x58(SP)    ; place the same vtable ptr as x in the slice
    LEAQ 0x20(SP), CX
    MOVQ CX, 0x60(SP)    ; place the address of z's copy in the slice

    MOVQ main.w(SB), CX  ; copy w on the stack, out of the slice
    MOVQ CX, 0x18(SP)
    MOVQ AX, 0x68(SP)    ; place the same vtable ptr as x in the slice
    LEAQ 0x18(SP), AX
    MOVQ AX, 0x70(SP)    ; place the address of w's copy in the slice

    LEAQ 0x38(SP), AX
    MOVQ AX, 0(SP)       ; set the slice base address as argument
    MOVQ $0x4, 0x8(SP)   ; the slice's size
    MOVQ $0x4, 0x10(SP)  ; the slice's capacity

    CALL main.g(SB)       ; call the function

    ; <function epilogue>
    RET

Exception handling

Implementation of defer

func Defer1() int { defer f(); return 123 }
Defer1:
       ; <function prologue>

       0x45c4bd         MOVQ $0x0, AX
       0x45c4c4         MOVQ AX, 0x8(SP)   ; set up a word full with zeroes
       0x45c4c9         MOVB $0x0, 0x7(SP) ; set the first byte to zero (redundant)

       ; write zero to the return value slot
       0x45c4ce         MOVQ $0x0, 0x20(SP)

       ; defer the call to f()
       0x45c4d7         LEAQ 0x1b672(IP), AX
       0x45c4de         MOVQ AX, 0x8(SP)      ; write the address of f
       0x45c4e3         MOVB $0x1, 0x7(SP)    ; let the runtime know there is 1 defer

       ; write the return value 123
       0x45c4e8         MOVQ $0x7b, 0x20(SP)

       ; un-defer
       0x45c4f1         MOVB $0x0, 0x7(SP)    ; let the runtime know there is no more defer
       ; final call to f() on the return path
       0x45c4f6         CALL main.f(SB)

       ; <function epilogue>
       0x45c504         RET

       ; the following code is called during unwinds after a recover,
       ; not on the common case:
       0x45c505         CALL runtime.deferreturn(SB)
       0x45c50a         MOVQ 0x10(SP), BP
       0x45c50f         ADDQ $0x18, SP
       0x45c513         RET

Implementation of panic

func Panic() { panic(123) }
Panic:
   ; <function prologue>

   ; load the vtable for interface{}:
   LEAQ 0x78dc(IP), AX
   MOVQ AX, 0(SP)

   ; load the address of a static copy of the
   ; integer value 123:
   LEAQ 0x2afa9(IP), AX
   MOVQ AX, 0x8(SP)

   ; call gopanic:
   CALL runtime.gopanic(SB)
   NOPL
   ; note: function epilogue omitted in this case

Catching exceptions: defer + recover

参考

关于我

85 后程序员, 比较熟悉 Java,JVM,Golang 相关技术栈, 关注 Liunx kernel,目前痴迷于分布式系统的设计和实践。 研究包括但不限于 Docker Kubernetes eBPF 等相关技术。

Blog

Code

Life

Archive