返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

8.1 调用约定

发布于 2024-10-12 19:16:00 字数 5122 浏览 0 评论 0 收藏 0

调用约定(Calling Conventions)是 应用二进制接口 (ABI)的组成部分,内容包括:

  • 参数和返回值如何传递?(寄存器还是调用栈)
  • 按什么顺序?
  • 谁负责内存分配和清理?

当我们反汇编,或者阅读 runtime 包内汇编代码时,可看到 ABIInternal 这个声明。
其全称是 “Internal Application Binary Interface”,简称 ABI,定义内存数据布局和函数调用约定。

ABI 定义并不稳定,比如 1.17 之前是 stack-based,而现在则是 register-based。
这个改变可带来 5% ~ 10% 左右的性能提升。
如编写 Go Assembly 代码,建议参考《 A Quick Guide to Go's Assembler 》,该文档为 ABI0 稳定版本。

本文仅对 AMD64 下的调用规范作简单说明和演示,更多细节,请阅读《 Go internal ABI specification 》。

专用寄存器:

  • RSP: stack pointer (fixed)
  • RBP: frame pointer (fixed)
  • RDX: closure context pointer
  • R12: None
  • R13: None
  • R14: current groutine
  • R15: GOT
  • X15: zero value (fixed)

调用寄存器:

  • integer: RAX, RBX, RCX, RDI, RSI, R8, R9, R10, R11
  • float: X0 ~ X14

参数超出寄存器数量,则入栈。

//go:noinline
func test(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 int) int {
	x := a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10
	return x
}

func main() {
	x := test(11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
	println(x)
}
$ go build -gcflags "-S"

"".main STEXT size=133 args=0x0 locals=0x60 funcid=0x0 align=0x0
    0x0000 TEXT    "".main(SB), ABIInternal, $96-0

    0x0006 SUBQ    $96, SP
    0x000a MOVQ    BP, 88(SP)
    0x000f LEAQ    88(SP), BP

    0x0014 MOVQ    $20, (SP)  ; a10 (stack)
    0x001c MOVL    $11, AX    ; a1
    0x0021 MOVL    $12, BX    ; a2
    0x0026 MOVL    $13, CX    ; a3
    0x002b MOVL    $14, DI    ; a4
    0x0030 MOVL    $15, SI    ; a5
    0x0035 MOVL    $16, R8    ; a6
    0x003b MOVL    $17, R9    ; a7
    0x0041 MOVL    $18, R10   ; a8
    0x0047 MOVL    $19, R11   ; a9

    0x004d CALL    "".test(SB)
    0x0052 MOVQ    AX, "".x+80(SP)  ; ret

    0x0057 CALL    runtime.printlock(SB)
    0x005c MOVQ    "".x+80(SP), AX
    0x0061 CALL    runtime.printint(SB)
    0x0066 CALL    runtime.printnl(SB)
    0x006b CALL    runtime.printunlock(SB)

    0x0070 MOVQ    88(SP), BP
    0x0075 ADDQ    $96, SP
    0x0079 RET


"".test STEXT nosplit size=35 args=0x50 locals=0x0 funcid=0x0 align=0x0
    0x0000  TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-80

    0x0000  LEAQ    (BX)(AX*1), DX    ; DX = AX + BX
    0x0004  ADDQ    DX, CX            ; CX += DX
    0x0007  ADDQ    DI, CX            ; CX += DI
    0x000a  ADDQ    SI, CX            ; CX += SI
    0x000d  ADDQ    R8, CX            ; CX += R8
    0x0010  ADDQ    R9, CX            ; CX += R9
    0x0013  ADDQ    R10, CX           ; CX += R10
    0x0016  ADDQ    R11, CX           ; CX += R11
    0x0019  MOVQ    "".a10+8(SP), DX  ; DX = a10, skip call_ret_pc
    0x001e  LEAQ    (DX)(CX*1), AX    ; AX = DX + CX, return
    0x0022  RET

对于复合类型,可使用多个相邻的寄存器。

type data struct {
	x int
	y int
}

//go:noinline
func test(a1 int, a2 data, a3 int) data {
	d := data {
		x: a1 + a2.x, 
		y: a3 + a2.y,
	}

	return d
}

func main() {
	d := data{100, 200}
	r := test(11, d, 13)
	println(r.x, r.y)
}
"".main STEXT size=112 args=0x0 locals=0x38 funcid=0x0 align=0x0
    0x0000 TEXT    "".main(SB), ABIInternal, $56-0

    0x0014 MOVL    $11,  AX      ; a1
    0x0019 MOVL    $100, BX      ; a2.x
    0x001e MOVL    $200, CX      ; a2.y
    0x0023 MOVL    $13,  DI      ; a3

    0x0028 CALL    "".test(SB)
    0x002d MOVQ    AX, ""..autotmp_8+40(SP)   ; ret.x
    0x0032 MOVQ    BX, ""..autotmp_9+32(SP)   ; ret.y

    0x0037 CALL    runtime.printlock(SB)
    0x003c MOVQ    ""..autotmp_8+40(SP), AX
    0x0041 CALL    runtime.printint(SB)
    0x0046 CALL    runtime.printsp(SB)
    0x004b MOVQ    ""..autotmp_9+32(SP), AX
    0x0050 CALL    runtime.printint(SB)
    0x0055 CALL    runtime.printnl(SB)
    0x005a CALL    runtime.printunlock(SB)

浮点参数或返回值使用 X 寄存器,可与 R 寄存器混编。

//go:noinline
func test(a1 int, a2 float32, a3 int, a4 float32) (int, float32) {
	return a1 + a3, a2 + a4
}

func main() {
	x, y := test(11, 1.0, 13, 2.0)
	println(x, y)
}
"".main STEXT size=133 args=0x0 locals=0x38 funcid=0x0 align=0x0
    0x0000 TEXT    "".main(SB), ABIInternal, $56-0

    0x0006 SUBQ    $56, SP
    0x000a MOVQ    BP, 48(SP)
    0x000f LEAQ    48(SP), BP

    0x0014 MOVL    $11, AX                  ; a1
    0x0019 MOVSS   $f32.3f800000(SB), X0    ; a2
    0x0021 MOVL    $13, BX                  ; a3
    0x0026 MOVSS   $f32.40000000(SB), X1    ; a4

    0x002e CALL    "".test(SB)
    0x0033 MOVQ    AX, "".x+40(SP)          ; ret.0
    0x0038 MOVSS   X0, "".y+36(SP)          ; ret.1


"".test STEXT nosplit size=8 args=0x20 locals=0x0 funcid=0x0 align=0x0
    0x0000  TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-32

    0x0000  ADDQ    BX, AX    ; ret.0 AX
    0x0003  ADDSS   X1, X0    ; ret.1 X0
    0x0007  RET

有关 stack-based 调用约定,请阅读《源码剖析》早期版本。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文