go 1.17 Register-based Go calling convention

官方博客: https://go.googlesource.com/proposal/+/master/design/40724-register-calling.md

测试平台:

NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

在go1.17之前,go的函数调用惯例是通过栈来传递参数、返回值。在go1.17的实现中,改变了这一惯例,采用寄存器的方式传递参数、返回值。
下面做一个小测试看看效果,分别采用两个go版本,编译相同的代码,查看汇编指令证明官方的说法,主要是用于学习。
go版本:

  • 1.16.2(作为对照)
  • 1.17.3

一个小的测试demo

package main

func main() {
    i := 1024
    j := test(i)
    println(j)
}

//go:noinline
func test(i int) int {
    return i + 1
}

使用1.16.2进行编译(go tool compile -S main.go),省略不相关输出

"".test STEXT nosplit size=14 args=0x10 locals=0x0 funcid=0x0
    0x0000 00000 (main.go:10)   TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-16
    0x0000 00000 (main.go:10)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:10)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:11)   MOVQ    "".i+8(SP), AX             // 这里将栈上存储的参数复制到寄存器AX
    0x0005 00005 (main.go:11)   INCQ    AX                              // 对寄存器AX上的数执行自增操作
    0x0008 00008 (main.go:11)   MOVQ    AX, "".~r1+16(SP)      // 将寄存器AX上的数,复制到目标返回值位置(栈上)
    0x000d 00013 (main.go:11)   RET
    0x0000 48 8b 44 24 08 48 ff c0 48 89 44 24 10 c3        H.D$.H..H.D$..

使用1.17.3编译:

"".test STEXT nosplit size=4 args=0x8 locals=0x0 funcid=0x0
    0x0000 00000 (main.go:10)   TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-8
    0x0000 00000 (main.go:10)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:10)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:10)   FUNCDATA    $5, "".test.arginfo1(SB)
    0x0000 00000 (main.go:11)   INCQ    AX        // 直接操作寄存器,并约定通过寄存器AX返回数据给调用方
    0x0003 00003 (main.go:11)   RET
    0x0000 48 ff c0 c3

下面看看多个返回值(以两个为例),go编译器怎么处理(1.16.2就不做演示了,主要看新版本的编译行为)。代码如下:

package main

func main() {
    i := 1024
        j, k := test(i)
    println(j, k)
}

//go:noinline
func test(i int) (int, int) {
    return i + 1, i + 2
}

"".test STEXT nosplit size=12 args=0x8 locals=0x0 funcid=0x0
    0x0000 00000 (main.go:10)   TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-8
    0x0000 00000 (main.go:10)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:10)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:10)   FUNCDATA    $5, "".test.arginfo1(SB)
    0x0000 00000 (main.go:11)   LEAQ    1(AX), CX    // CX = AX + 1
    0x0004 00004 (main.go:11)   LEAQ    2(AX), BX    // BX = AX + 2 
    0x0008 00008 (main.go:11)   MOVQ    CX, AX        // AX = CX
    0x000b 00011 (main.go:11)   RET
    0x0000 48 8d 48 01 48 8d 58 02 48 89 c8 c3              H.H.H.X.H...

可见第一个返回值放在AX寄存器,第二个返回值放在BX。这样看来,多个返回值也会通过寄存器传递,我们来试试再多一些返回值,会是怎么样的(比如7个),代码如下:

package main

func main() {
    i := 1024
    println(test(i))
}

//go:noinline
func test(i int) (int, int, int, int, int, int, int) {
    return i + 1, i + 2, i + 3, i + 4, i + 5, i+ 6, i + 7
}

"".test STEXT nosplit size=33 args=0x8 locals=0x0 funcid=0x0
    0x0000 00000 (main.go:9)    TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-8
    0x0000 00000 (main.go:9)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:9)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:9)    FUNCDATA    $5, "".test.arginfo1(SB)
    0x0000 00000 (main.go:10)   LEAQ    1(AX), DX
    0x0004 00004 (main.go:10)   LEAQ    2(AX), BX
    0x0008 00008 (main.go:10)   LEAQ    3(AX), CX
    0x000c 00012 (main.go:10)   LEAQ    4(AX), DI
    0x0010 00016 (main.go:10)   LEAQ    5(AX), SI
    0x0014 00020 (main.go:10)   LEAQ    6(AX), R8
    0x0018 00024 (main.go:10)   LEAQ    7(AX), R9
    0x001c 00028 (main.go:10)   MOVQ    DX, AX
    0x001f 00031 (main.go:10)   NOP
    0x0020 00032 (main.go:10)   RET
    0x0000 48 8d 50 01 48 8d 58 02 48 8d 48 03 48 8d 78 04  H.P.H.X.H.H.H.x.
    0x0010 48 8d 70 05 4c 8d 40 06 4c 8d 48 07 48 89 d0 90  H.p.L.@.L.H.H...
    0x0020 c3

看来7个返回值也都会通过寄存器传递,按照返回值的顺序使用寄存器的顺序是: AX、BX、CX、DI、SI、R8、R9

再次增加返回值数量,达到14个

package main

func main() {
    i := 1024
    println(test(i))
}

//go:noinline
func test(i int) (int, int, int, int, int, int, int, int, int, int, int, int, int, int) {
    return i + 1, i + 2, i + 3, i + 4, i + 5, i+ 6, i + 7, i + 8, i + 9, i + 10, i + 11, i + 12, i+ 13, i + 14
}

"".test STEXT nosplit size=85 args=0x30 locals=0x0 funcid=0x0
    0x0000 00000 (main.go:9)    TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-48
    0x0000 00000 (main.go:9)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:9)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (main.go:9)    FUNCDATA    $5, "".test.arginfo1(SB)
    0x0000 00000 (main.go:10)   LEAQ    10(AX), DX
    0x0004 00004 (main.go:10)   MOVQ    DX, "".~r10+8(SP)
    0x0009 00009 (main.go:10)   LEAQ    11(AX), DX
    0x000d 00013 (main.go:10)   MOVQ    DX, "".~r11+16(SP)
    0x0012 00018 (main.go:10)   LEAQ    12(AX), DX
    0x0016 00022 (main.go:10)   MOVQ    DX, "".~r12+24(SP)
    0x001b 00027 (main.go:10)   LEAQ    13(AX), DX
    0x001f 00031 (main.go:10)   MOVQ    DX, "".~r13+32(SP)
    0x0024 00036 (main.go:10)   LEAQ    14(AX), DX
    0x0028 00040 (main.go:10)   MOVQ    DX, "".~r14+40(SP)
    0x002d 00045 (main.go:10)   LEAQ    1(AX), DX
    0x0031 00049 (main.go:10)   LEAQ    2(AX), BX
    0x0035 00053 (main.go:10)   LEAQ    3(AX), CX
    0x0039 00057 (main.go:10)   LEAQ    4(AX), DI
    0x003d 00061 (main.go:10)   LEAQ    5(AX), SI
    0x0041 00065 (main.go:10)   LEAQ    6(AX), R8
    0x0045 00069 (main.go:10)   LEAQ    7(AX), R9
    0x0049 00073 (main.go:10)   LEAQ    8(AX), R10
    0x004d 00077 (main.go:10)   LEAQ    9(AX), R11
    0x0051 00081 (main.go:10)   MOVQ    DX, AX
    0x0054 00084 (main.go:10)   RET
    0x0000 48 8d 50 0a 48 89 54 24 08 48 8d 50 0b 48 89 54  H.P.H.T$.H.P.H.T
    0x0010 24 10 48 8d 50 0c 48 89 54 24 18 48 8d 50 0d 48  $.H.P.H.T$.H.P.H
    0x0020 89 54 24 20 48 8d 50 0e 48 89 54 24 28 48 8d 50  .T$ H.P.H.T$(H.P
    0x0030 01 48 8d 58 02 48 8d 48 03 48 8d 78 04 48 8d 70  .H.X.H.H.H.x.H.p
    0x0040 05 4c 8d 40 06 4c 8d 48 07 4c 8d 50 08 4c 8d 58  .L.@.L.H.L.P.L.X
    0x0050 09 48 89 d0 c3                                   .H...

能看出,go支持寄存器传递返回值的最大数量是9个,如果返回值多余9个,剩余的返回值还是通过栈传递。在执行顺序上,本例中先处理第10个及以后的返回值,然后处理前9个返回值。按照返回值的顺序使用寄存器的顺序是: AX、BX、CX、DI、SI、R8、R9、R10、R11

多个返回值使用寄存器的情况,我们弄清楚了,下面看看多参数的情况,这次直接使用11个参数看看。

package main

func main() {
    println(test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
}

//go:noinline
func test(i, j, k, l, m, n, p, q, r, s, t int) (int) {
    return i + j + k + l + m + n + p + q + r + s + t
}

"".main STEXT size=141 args=0x0 locals=0x68 funcid=0x0
        ...
    0x0014 00020 (main.go:3)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0014 00020 (main.go:4)    MOVQ    $10, (SP)
    0x001c 00028 (main.go:4)    MOVQ    $11, 8(SP)
    0x0025 00037 (main.go:4)    MOVL    $1, AX
    0x002a 00042 (main.go:4)    MOVL    $2, BX
    0x002f 00047 (main.go:4)    MOVL    $3, CX
    0x0034 00052 (main.go:4)    MOVL    $4, DI
    0x0039 00057 (main.go:4)    MOVL    $5, SI
    0x003e 00062 (main.go:4)    MOVL    $6, R8
    0x0044 00068 (main.go:4)    MOVL    $7, R9
    0x004a 00074 (main.go:4)    MOVL    $8, R10
    0x0050 00080 (main.go:4)    MOVL    $9, R11
    0x0056 00086 (main.go:4)    PCDATA  $1, $0
    0x0056 00086 (main.go:4)    CALL    "".test(SB)
    0x005b 00091 (main.go:4)    MOVQ    AX, ""..autotmp_1+88(SP)
        ...
"".test STEXT nosplit size=43 args=0x58 locals=0x0 funcid=0x0
        ...
    0x0000 00000 (main.go:8)    FUNCDATA    $5, "".test.arginfo1(SB)
    0x0000 00000 (main.go:9)    LEAQ    (BX)(AX*1), DX
    0x0004 00004 (main.go:9)    ADDQ    DX, CX
    0x0007 00007 (main.go:9)    ADDQ    DI, CX
    0x000a 00010 (main.go:9)    ADDQ    SI, CX
    0x000d 00013 (main.go:9)    ADDQ    R8, CX
    0x0010 00016 (main.go:9)    ADDQ    R9, CX
    0x0013 00019 (main.go:9)    ADDQ    R10, CX
    0x0016 00022 (main.go:9)    ADDQ    R11, CX
    0x0019 00025 (main.go:9)    MOVQ    "".s+8(SP), DX
    0x001e 00030 (main.go:9)    ADDQ    DX, CX
    0x0021 00033 (main.go:9)    MOVQ    "".t+16(SP), DX
    0x0026 00038 (main.go:9)    LEAQ    (DX)(CX*1), AX
    0x002a 00042 (main.go:9)    RET
    0x0000 48 8d 14 03 48 01 d1 48 01 f9 48 01 f1 4c 01 c1  H...H..H..H..L..

可以看到,前9个参数通过寄存器传递,后面的参数通过栈传递;另外参数的顺序对应使用寄存器的顺序为:AX、BX、CX、DI、SI、R8、R9、R10、R11

从上面可以得出结论,在当前系统架构、go语言版本的情况下,go编译器会将前9个参数、前9个返回值通过寄存器传递,多出的参数、返回值则通过栈进行传递。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 王爽汇编全书知识点大纲 第一章 基础知识 机器语言 汇编语言的产生 汇编语言的组成 存储器 cpu对存储器的读写 ...
    2c3ba901516f阅读 2,405评论 0 1
  • ps:这是我19年的写的总结,编辑成了pdf,当时用了很多截图,导致没法复制源码,原来注释的代码也找不到了,只能将...
    蟹蟹宁阅读 1,304评论 0 2
  • 为什么要了解汇编? 了解汇编语言能够更加深入的理解高级语言的本质,彻底理解之前只是知道却又不清楚为什么的知识,比如...
    喵子G阅读 1,809评论 0 1
  • TITLE: 编程语言乱炖 码农最大的烦恼——编程语言太多。不是我不学习,这世界变化快! 有时候还是蛮怀念十几、二...
    码园老农阅读 5,295评论 2 35
  • 第一章: Go 编译入门 在开始深入研究运行时类库和标准类库之前,学习一些go的抽象汇编语言是有必要的。这份快速指...
    链人成长chainerup阅读 605评论 0 0