6

go calling convention

 2 years ago
source link: http://hushi55.github.io/2021/05/20/go-calling-convention-x86-64
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

go calling convention

2021-05-20

这篇文章翻译自 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:

  • 一个函数使用本地变量,需要通过 SP 寄存器相对位置来获取,这个总是出现在前置序言中。
  • 和以前一样,每一个函数会设置一个帧指针 BP 寄存器,来方便异常释放。
  • 和以前一样,一个函数需要使用更多的栈空间时,这样的情况下需要检查当前栈上的剩余空间,如果不足就需要分配更多的空间。 这是因为 go 默认为 goroutines 分配很小的栈空间。

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

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



  1. internal/cpu.Initialize:
  2. ; Check remaining stack size:
  3. MOVQ FS:0xfffffff8, CX
  4. CMPQ 0x10(CX), SP ; at least 24 bytes on the stack?
  5. JBE 0x401047 ; no: go to block at end of function below
  6. ; Allocate activation record:
  7. SUBQ $0x18, SP ; 24 bytes in activation record
  8. ; Set up the frame pointer
  9. MOVQ BP, 0x10(SP) ; BP is callee-save: store it
  10. LEAQ 0x10(SP), BP ; set up new frame pointer
  11. ...
  12. MOVQ 0x10(SP), BP ; restore the caller's frame pointer
  13. ADDQ $0x18, SP ; deallocate the activation record
  14. RET ; return
  15. 0x401047:
  16. CALL runtime.morestack_noctxt(SB) ; alloc more stack
  17. JMP internal/cpu.Initialize(SB) ; restart

Callee-save registers—or not

The cost of pointers and interfaces



  1. // Define a struct type implementing the interface by value.
  2. type bar struct{ x int }
  3. func (bar) foo() {}
  4. // Define a global variable so we don't use the heap allocator.
  5. var y bar
  6. // Make an interface value.
  7. func MakeInterface2() Foo { return y }


  1. MakeInterface2:
  2. ; <function prologue>
  3. ; write y to 0(SP), as an argument to runtime.convT64
  4. 0x45c55d 488b057cc70900 MOVQ main.y(SB), AX
  5. 0x45c564 48890424 MOVQ AX, 0(SP)
  6. ; call runtime.convT64, this converts the object to a heap reference
  7. 0x45c568 e833c5faff CALL runtime.convT64(SB)
  8. ; extract the return value
  9. 0x45c56d 488b442408 MOVQ 0x8(SP), AX
  10. ; take the vtable pointer
  11. 0x45c572 488d0d07c00200 LEAQ go.itab.main.bar,main.Foo(SB), CX
  12. ; write both to the return value slot for MakeInterface2
  13. 0x45c579 48894c2420 MOVQ CX, 0x20(SP)
  14. 0x45c57e 4889442428 MOVQ AX, 0x28(SP)
  15. ; <function epilogue>
  16. 0x45c58c c3 RET


  1. MakeInterface2:
  2. ; <function prologue>
  3. ; take the vtable pointer
  4. 0x4805dd 488d053c020400 LEAQ go.itab.src.bar,src.Foo(SB), AX
  5. ; pass it as argument to convT2I64
  6. 0x4805e4 48890424 MOVQ AX, 0(SP)
  7. ; take the address of y
  8. 0x4805e8 488d05e9f10b00 LEAQ main.y(SB), AX
  9. ; pass it as argument to convT2I64
  10. 0x4805ef 4889442408 MOVQ AX, 0x8(SP)
  11. ; convert to interface reference
  12. 0x4805f4 e8e7b2f8ff CALL runtime.convT2I64(SB)
  13. ; copy the return value from runtime.convT2I64 to the return slot of MakeInterface2
  14. 0x4805f9 488b442410 MOVQ 0x10(SP), AX
  15. 0x4805fe 488b4c2418 MOVQ 0x18(SP), CX
  16. 0x480603 4889442430 MOVQ AX, 0x30(SP)
  17. 0x480608 48894c2438 MOVQ CX, 0x38(SP)
  18. ; <function epilogue>
  19. 0x480616 c3 RET

Vararg calls



  1. func f(...int) {}
  2. var x,y,z,w int
  3. func caller() {
  4. f(x,y,z,w)
  5. }


  1. caller:
  2. ; <function prologue>
  3. ; fill the slice:
  4. XORPS X0, X0 ; set 2 words (128 bit) to zero in X0
  5. MOVUPS X0, 0x18(SP) ; initialize the 4-element slice to zero
  6. MOVUPS X0, 0x28(SP) ; initialize the 4-element slice to zero
  7. MOVQ main.x(SB), AX
  8. MOVQ AX, 0x18(SP) ; store x into 1st position
  9. MOVQ main.y(SB), AX
  10. MOVQ AX, 0x20(SP) ; store y into 2nd position
  11. MOVQ main.z(SB), AX
  12. MOVQ AX, 0x28(SP) ; store z into 3rd position
  13. MOVQ main.w(SB), AX
  14. MOVQ AX, 0x30(SP) ; store w into 4th position
  15. ; prepare the slice as outgoing argument:
  16. LEAQ 0x18(SP), AX ; store the base address
  17. MOVQ AX, 0(SP)
  18. MOVQ $0x4, 0x8(SP) ; store the length
  19. MOVQ $0x4, 0x10(SP) ; store the capacity
  20. CALL main.g(SB) ; call the function
  21. ; <function epilogue>
  22. RET


  1. // note: now we have an interface type.
  2. func f(...interface{}) {}
  3. var x,y,z,w int
  4. func caller() {
  5. f(x,y,z,w)
  6. }


  1. caller:
  2. ; <function prologue>
  3. ; fill the slice:
  4. XORPS X0, X0 ; zero out the slice
  5. MOVUPS X0, 0x38(SP)
  6. MOVUPS X0, 0x48(SP)
  7. MOVUPS X0, 0x58(SP)
  8. MOVUPS X0, 0x68(SP)
  9. MOVQ main.x(SB), AX
  10. MOVQ AX, 0x30(SP) ; copy x on the stack, out of the slice
  11. LEAQ 0x7995(IP), AX
  12. MOVQ AX, 0x38(SP) ; place x's interface{} vtable ptr in the slice
  13. LEAQ 0x30(SP), CX
  14. MOVQ CX, 0x40(SP) ; place the address of x's copy in the slice
  15. MOVQ main.y(SB), CX
  16. MOVQ CX, 0x28(SP) ; copy y on the stack, out of the slice
  17. MOVQ AX, 0x48(SP) ; place the same vtable ptr as x in the slice
  18. LEAQ 0x28(SP), CX
  19. MOVQ CX, 0x50(SP) ; place the address of y's copy in the slice
  20. MOVQ main.z(SB), CX
  21. MOVQ CX, 0x20(SP) ; copy z on the stack, out of the slice
  22. MOVQ AX, 0x58(SP) ; place the same vtable ptr as x in the slice
  23. LEAQ 0x20(SP), CX
  24. MOVQ CX, 0x60(SP) ; place the address of z's copy in the slice
  25. MOVQ main.w(SB), CX ; copy w on the stack, out of the slice
  26. MOVQ CX, 0x18(SP)
  27. MOVQ AX, 0x68(SP) ; place the same vtable ptr as x in the slice
  28. LEAQ 0x18(SP), AX
  29. MOVQ AX, 0x70(SP) ; place the address of w's copy in the slice
  30. LEAQ 0x38(SP), AX
  31. MOVQ AX, 0(SP) ; set the slice base address as argument
  32. MOVQ $0x4, 0x8(SP) ; the slice's size
  33. MOVQ $0x4, 0x10(SP) ; the slice's capacity
  34. CALL main.g(SB) ; call the function
  35. ; <function epilogue>
  36. RET

Exception handling

Implementation of defer



  1. func Defer1() int { defer f(); return 123 }


  1. Defer1:
  2. ; <function prologue>
  3. 0x45c4bd MOVQ $0x0, AX
  4. 0x45c4c4 MOVQ AX, 0x8(SP) ; set up a word full with zeroes
  5. 0x45c4c9 MOVB $0x0, 0x7(SP) ; set the first byte to zero (redundant)
  6. ; write zero to the return value slot
  7. 0x45c4ce MOVQ $0x0, 0x20(SP)
  8. ; defer the call to f()
  9. 0x45c4d7 LEAQ 0x1b672(IP), AX
  10. 0x45c4de MOVQ AX, 0x8(SP) ; write the address of f
  11. 0x45c4e3 MOVB $0x1, 0x7(SP) ; let the runtime know there is 1 defer
  12. ; write the return value 123
  13. 0x45c4e8 MOVQ $0x7b, 0x20(SP)
  14. ; un-defer
  15. 0x45c4f1 MOVB $0x0, 0x7(SP) ; let the runtime know there is no more defer
  16. ; final call to f() on the return path
  17. 0x45c4f6 CALL main.f(SB)
  18. ; <function epilogue>
  19. 0x45c504 RET
  20. ; the following code is called during unwinds after a recover,
  21. ; not on the common case:
  22. 0x45c505 CALL runtime.deferreturn(SB)
  23. 0x45c50a MOVQ 0x10(SP), BP
  24. 0x45c50f ADDQ $0x18, SP
  25. 0x45c513 RET

Implementation of panic



  1. func Panic() { panic(123) }


  1. Panic:
  2. ; <function prologue>
  3. ; load the vtable for interface{}:
  4. LEAQ 0x78dc(IP), AX
  5. MOVQ AX, 0(SP)
  6. ; load the address of a static copy of the
  7. ; integer value 123:
  8. LEAQ 0x2afa9(IP), AX
  9. MOVQ AX, 0x8(SP)
  10. ; call gopanic:
  11. CALL runtime.gopanic(SB)
  12. NOPL
  13. ; note: function epilogue omitted in this case

Catching exceptions: defer + recover


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK