彻底搞懂Go泛型中dict的数据结构

Why为什么要研究dict的结构?
Go泛型实现并没有对dict的数据结构进行统一的限制,而是针对不同的gcshape生成不同的dict数据,并存储在只读数据区。为了研究Go泛型实现中关于虚拟方法表的具体实现,需要弄清楚dict的内存数据到底是什么样,已经如何通过dict进行方法跳转的。

单态化:

单一泛型参数

代码:

cat monomorphisation.go
package main

func main() {
    foo(123)
    foo("hello")

    var i interface{} = 1
    print(i)
}

//go:noinline
func foo[T int | string](v T) {
    println(v)
}

调试验证:

[root@bjbd-infra-prod-k8s-client-1 generic]# dlv debug monomorphisation.go
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x46e40a for main.main() ./monomorphisation.go:3
(dlv) c
> [Breakpoint 1] main.main() ./monomorphisation.go:3 (hits goroutine(1):1 total:1) (PC: 0x46e40a)
     1: package main
     2:
=>   3: func main() {
     4:     foo(123)
     5:     foo("hello")
     6:
     7:     var i interface{} = 1
     8:     print(i)
(dlv) disass
TEXT main.main(SB) /root/project/go/src/github.com/qshuai/test/generic/monomorphisation.go
    monomorphisation.go:3   0x46e400    493b6610    cmp rsp, qword ptr [r14+0x10]
    monomorphisation.go:3   0x46e404    766a        jbe 0x46e470
    monomorphisation.go:3   0x46e406    55      push rbp
    monomorphisation.go:3   0x46e407    4889e5      mov rbp, rsp
=>  monomorphisation.go:3   0x46e40a*   4883ec28    sub rsp, 0x28
    monomorphisation.go:4   0x46e40e    488d0593e80100  lea rax, ptr [rip+0x1e893]
    monomorphisation.go:4   0x46e415    bb7b000000  mov ebx, 0x7b
    monomorphisation.go:4   0x46e41a    e8e1000000  call $main.foo[go.shape.int]
    monomorphisation.go:5   0x46e41f    488d058ae80100  lea rax, ptr [rip+0x1e88a]
    monomorphisation.go:5   0x46e426    488d1d0e420100  lea rbx, ptr [rip+0x1420e]
    monomorphisation.go:5   0x46e42d    b905000000  mov ecx, 0x5
    monomorphisation.go:5   0x46e432    e849000000  call $main.foo[go.shape.string]
    monomorphisation.go:7   0x46e437    488d0d62730000  lea rcx, ptr [rip+0x7362]
    monomorphisation.go:7   0x46e43e    48894c2418  mov qword ptr [rsp+0x18], rcx
    monomorphisation.go:7   0x46e443    488d0d56e80100  lea rcx, ptr [rip+0x1e856]
    monomorphisation.go:7   0x46e44a    48894c2420  mov qword ptr [rsp+0x20], rcx
    monomorphisation.go:8   0x46e44f    e82c7cfcff  call $runtime.printlock
    monomorphisation.go:8   0x46e454    488b442418  mov rax, qword ptr [rsp+0x18]
    monomorphisation.go:8   0x46e459    488b5c2420  mov rbx, qword ptr [rsp+0x20]
    monomorphisation.go:8   0x46e45e    6690        data16 nop
    monomorphisation.go:8   0x46e460    e8bb85fcff  call $runtime.printeface
    monomorphisation.go:8   0x46e465    e8767cfcff  call $runtime.printunlock
    monomorphisation.go:9   0x46e46a    4883c428    add rsp, 0x28
    monomorphisation.go:9   0x46e46e    5d      pop rbp
    monomorphisation.go:9   0x46e46f    c3      ret
    monomorphisation.go:3   0x46e470    e8abaeffff  call $runtime.morestack_noctxt
    monomorphisation.go:3   0x46e475    eb89        jmp $main.main
(dlv) b *0x46e415
Breakpoint 2 set at 0x46e415 for main.main() ./monomorphisation.go:4
(dlv) c
> [Breakpoint 2] main.main() ./monomorphisation.go:4 (hits goroutine(1):1 total:1) (PC: 0x46e415)
     1: package main
     2:
     3: func main() {
=>   4:     foo(123)
     5:     foo("hello")
     6:
     7:     var i interface{} = 1
     8:     print(i)
     9: }
(dlv) disass
TEXT main.main(SB) /root/project/go/src/github.com/qshuai/test/generic/monomorphisation.go
    monomorphisation.go:3   0x46e400    493b6610    cmp rsp, qword ptr [r14+0x10]
    monomorphisation.go:3   0x46e404    766a        jbe 0x46e470
    monomorphisation.go:3   0x46e406    55      push rbp
    monomorphisation.go:3   0x46e407    4889e5      mov rbp, rsp
    monomorphisation.go:3   0x46e40a*   4883ec28    sub rsp, 0x28
    monomorphisation.go:4   0x46e40e    488d0593e80100  lea rax, ptr [rip+0x1e893]
=>  monomorphisation.go:4   0x46e415*   bb7b000000  mov ebx, 0x7b
    monomorphisation.go:4   0x46e41a    e8e1000000  call $main.foo[go.shape.int]
    monomorphisation.go:5   0x46e41f    488d058ae80100  lea rax, ptr [rip+0x1e88a]
    monomorphisation.go:5   0x46e426    488d1d0e420100  lea rbx, ptr [rip+0x1420e]
    monomorphisation.go:5   0x46e42d    b905000000  mov ecx, 0x5
    monomorphisation.go:5   0x46e432    e849000000  call $main.foo[go.shape.string]
    monomorphisation.go:7   0x46e437    488d0d62730000  lea rcx, ptr [rip+0x7362]
    monomorphisation.go:7   0x46e43e    48894c2418  mov qword ptr [rsp+0x18], rcx
    monomorphisation.go:7   0x46e443    488d0d56e80100  lea rcx, ptr [rip+0x1e856]
    monomorphisation.go:7   0x46e44a    48894c2420  mov qword ptr [rsp+0x20], rcx
    monomorphisation.go:8   0x46e44f    e82c7cfcff  call $runtime.printlock
    monomorphisation.go:8   0x46e454    488b442418  mov rax, qword ptr [rsp+0x18]
    monomorphisation.go:8   0x46e459    488b5c2420  mov rbx, qword ptr [rsp+0x20]
    monomorphisation.go:8   0x46e45e    6690        data16 nop
    monomorphisation.go:8   0x46e460    e8bb85fcff  call $runtime.printeface
    monomorphisation.go:8   0x46e465    e8767cfcff  call $runtime.printunlock
    monomorphisation.go:9   0x46e46a    4883c428    add rsp, 0x28
    monomorphisation.go:9   0x46e46e    5d      pop rbp
    monomorphisation.go:9   0x46e46f    c3      ret
    monomorphisation.go:3   0x46e470    e8abaeffff  call $runtime.morestack_noctxt
    monomorphisation.go:3   0x46e475    eb89        jmp $main.main
(dlv) p %x RAX
48cca8
(dlv) x -fmt hex -count 1 -size 8 0x48cca8
0x48cca8:   0x00000000004757a0
(dlv) p *(*abi.Type)(0x4757a0)
internal/abi.Type {Size_: 8, PtrBytes: 0, Hash: 3413333906, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed|TFlagRegularMemory (15), Align_: 8, FieldAlign_: 8, Kind_: Int (2), Equal: runtime.memequal64, GCData: *0, Str: 412, PtrToThis: 14368}
(dlv) list
> [Breakpoint 2] main.main() ./monomorphisation.go:4 (hits goroutine(1):1 total:1) (PC: 0x46e415)
     1: package main
     2:
     3: func main() {
=>   4:     foo(123)
     5:     foo("hello")
     6:
     7:     var i interface{} = 1
     8:     print(i)
     9: }
(dlv) b monomorphisation.go:8
Breakpoint 3 set at 0x46e44f for main.main() ./monomorphisation.go:8
(dlv) c
123
hello
> [Breakpoint 3] main.main() ./monomorphisation.go:8 (hits goroutine(1):1 total:1) (PC: 0x46e44f)
     3: func main() {
     4:     foo(123)
     5:     foo("hello")
     6:
     7:     var i interface{} = 1
=>   8:     print(i)
     9: }
    10:
    11: //go:noinline
    12: func foo[T int | string](v T) {
    13:     println(v)
(dlv) p *(*(**abi.Type)(uintptr(&i)))
internal/abi.Type {Size_: 8, PtrBytes: 0, Hash: 3413333906, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed|TFlagRegularMemory (15), Align_: 8, FieldAlign_: 8, Kind_: Int (2), Equal: runtime.memequal64, GCData: *0, Str: 412, PtrToThis: 14368}
(dlv) p %x *(*uint)(uintptr(&i))
4757a0

关于p %x *(*uint)(uintptr(&i))的解释:i为空接口类型,其第一个字段为类型元数据指针,&i则为元数据指针的指针,那么解引用一次就会获得元数据指针的值,元数据指针的值就是元数据所在的内存地址。

  1. 我们知道泛型调用时第一个参数是dict的指针,而go1.16之后采用寄存器传参,第一个参数位于AX寄存器,所以这条指令就是为了将dict指针赋值给第一个参数:monomorphisation.go:4 0x46e36e 488d0523d90100 lea rax, ptr [rip+0x1d923]
  2. 赋值后,我们获取AX寄存器中的值,就是dict的指针数据:p %x RAX
  3. 取指针指向数据的8个字节,我们猜测是int的类型元数据指针:x -fmt hex -count 1 -size 8 0x48bc98
  4. 我们将指针数据转化为类型元数据结构体: p *(*abi.Type)(0x4747a0)
  5. 结果显示Kind为int,基本证实了我们的猜测。为了以防万一,我们在相同程序中获取一个真实的int类型元数据,结果显示两者的类型元数据一样(类型元数据相同,且类型元数据指针相同)。

那么,基于以上事实我们推断dict的结构如下:

type main..dict.foo[int] struct {
    T1 *abi.Type
}

可能在类型元数据指针后面仍然存在其他信息字段,不过目前我们先到这里。

两个泛型参数:

第一个示例我们的指令都很详细,考虑到篇幅太大不方便阅读,剩下的示例将只会防止重点指令和结果。
代码:

package main

func main() {
    foo(123, "hello")
}

//go:noinline
func foo[T1 int | string, T2 int | string](a T1, b T2) {
    println(a, b)
}

调试:

(dlv) disass
TEXT main.main(SB) /root/project/go/src/github.com/qshuai/test/generic/monomorphisation2.go
    monomorphisation2.go:3  0x46e360    493b6610    cmp rsp, qword ptr [r14+0x10]
    monomorphisation2.go:3  0x46e364    762b        jbe 0x46e391
    monomorphisation2.go:3  0x46e366    55      push rbp
    monomorphisation2.go:3  0x46e367    4889e5      mov rbp, rsp
    monomorphisation2.go:3  0x46e36a*   4883ec20    sub rsp, 0x20
    monomorphisation2.go:4  0x46e36e    488d050bda0100  lea rax, ptr [rip+0x1da0b]
=>  monomorphisation2.go:4  0x46e375*   bb7b000000  mov ebx, 0x7b
    monomorphisation2.go:4  0x46e37a    488d0dba320100  lea rcx, ptr [rip+0x132ba]
    monomorphisation2.go:4  0x46e381    bf05000000  mov edi, 0x5
    monomorphisation2.go:4  0x46e386    e815000000  call $main.foo[go.shape.int,go.shape.string]
    monomorphisation2.go:5  0x46e38b    4883c420    add rsp, 0x20
    monomorphisation2.go:5  0x46e38f    5d      pop rbp
    monomorphisation2.go:5  0x46e390    c3      ret
    monomorphisation2.go:3  0x46e391    e8eaaeffff  call $runtime.morestack_noctxt
    monomorphisation2.go:3  0x46e396    ebc8        jmp $main.main
(dlv) p %x RAX
48bd80
(dlv) x -fmt hex -count 1 -size 8 0x48bd80
0x48bd80:   0x00000000004747a0
(dlv) p *(*abi.Type)(0x4747a0)
internal/abi.Type {Size_: 8, PtrBytes: 0, Hash: 3413333906, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed|TFlagRegularMemory (15), Align_: 8, FieldAlign_: 8, Kind_: Int (2), Equal: runtime.memequal64, GCData: *0, Str: 412, PtrToThis: 14368}
(dlv) x -fmt hex -count 1 -size 8 0x48bd88
0x48bd88:   0x0000000000474560
(dlv) p *(*abi.Type)(0x474560)
internal/abi.Type {Size_: 16, PtrBytes: 8, Hash: 125357496, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed (7), Align_: 8, FieldAlign_: 8, Kind_: String (24), Equal: runtime.strequal, GCData: *1, Str: 2440, PtrToThis: 13792}

其中0x48bd80为dict数据所在的内存地址,偏移8字节后即为0x48bd88
可以看出dict数据第一个字段为int类型元数据指针,第二个字段为string类型元数据指针,所以在这个场景下,dict中会一次存储类型参数对应的类型元数据指针。所以dict的结构如下:

type main..dict.foo[int,string] struct {
    T1 *abi.Type
    T2 *abi.Type
}

虚拟方法表:

代码:

package main

import (
        "io"
)

type foo[T string] struct{}

//go:noinline
func (f foo[T]) Read(p []byte) (n int, err error) {
        return len(p), nil
}

//go:noinline
func Read[T io.Reader](r T) {
        r.Read(nil)
}

func main() {
        f := foo[string]{}
        Read(f)
}

实现解析:

[root@bjbd-infra-prod-k8s-client-1 generic]# dlv debug receiver.go
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x46fd8a for main.main() ./receiver.go:19
(dlv) c
> [Breakpoint 1] main.main() ./receiver.go:19 (hits goroutine(1):1 total:1) (PC: 0x46fd8a)
    14: //go:noinline
    15: func Read[T io.Reader](r T) {
    16:     r.Read(nil)
    17: }
    18:
=>  19: func main() {
    20:     f := foo[string]{}
    21:     Read(f)
    22: }
    23:
(dlv) disass
TEXT main.main(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
    receiver.go:19  0x46fd80    493b6610    cmp rsp, qword ptr [r14+0x10]
    receiver.go:19  0x46fd84    761b        jbe 0x46fda1
    receiver.go:19  0x46fd86    55      push rbp
    receiver.go:19  0x46fd87    4889e5      mov rbp, rsp
=>  receiver.go:19  0x46fd8a*   4883ec08    sub rsp, 0x8
    receiver.go:21  0x46fd8e    488d050bde0100  lea rax, ptr [rip+0x1de0b]
    receiver.go:21  0x46fd95    e886000000  call $main.Read[go.shape.struct {}]
    receiver.go:22  0x46fd9a    4883c408    add rsp, 0x8
    receiver.go:22  0x46fd9e    5d      pop rbp
    receiver.go:22  0x46fd9f    90      nop
    receiver.go:22  0x46fda0    c3      ret
    receiver.go:19  0x46fda1    e87a97ffff  call $runtime.morestack_noctxt
    receiver.go:19  0x46fda6    ebd8        jmp $main.main
(dlv) b *0x46fd95
Breakpoint 2 set at 0x46fd95 for main.main() ./receiver.go:21
(dlv) c
> [Breakpoint 2] main.main() ./receiver.go:21 (hits goroutine(1):1 total:1) (PC: 0x46fd95)
    16:     r.Read(nil)
    17: }
    18:
    19: func main() {
    20:     f := foo[string]{}
=>  21:     Read(f)
    22: }
    23:
(dlv) disass
TEXT main.main(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
    receiver.go:19  0x46fd80    493b6610    cmp rsp, qword ptr [r14+0x10]
    receiver.go:19  0x46fd84    761b        jbe 0x46fda1
    receiver.go:19  0x46fd86    55      push rbp
    receiver.go:19  0x46fd87    4889e5      mov rbp, rsp
    receiver.go:19  0x46fd8a*   4883ec08    sub rsp, 0x8
    receiver.go:21  0x46fd8e    488d050bde0100  lea rax, ptr [rip+0x1de0b]
=>  receiver.go:21  0x46fd95*   e886000000  call $main.Read[go.shape.struct {}]
    receiver.go:22  0x46fd9a    4883c408    add rsp, 0x8
    receiver.go:22  0x46fd9e    5d      pop rbp
    receiver.go:22  0x46fd9f    90      nop
    receiver.go:22  0x46fda0    c3      ret
    receiver.go:19  0x46fda1    e87a97ffff  call $runtime.morestack_noctxt
    receiver.go:19  0x46fda6    ebd8        jmp $main.main
(dlv) p %x RAX
48dba0
(dlv) x -fmt hex -count 4 -size 8 0x48dba0
0x48dba0:   0x000000000046fe60   0x0000000000478ee0   0x00000000004899bc   0x0000000000000045
(dlv) p *(*abi.Type)(0x478ee0)
internal/abi.Type {Size_: 0, PtrBytes: 0, Hash: 2310965435, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed|TFlagRegularMemory (15), Align_: 1, FieldAlign_: 1, Kind_: internal/reflectlite.Struct (25), Equal: runtime.memequal0, GCData: *0, Str: 9507, PtrToThis: 31808}
(dlv) si
> main.Read[go.shape.struct {}]() ./receiver.go:15 (PC: 0x46fe20)
TEXT main.Read[go.shape.struct {}](SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:15  0x46fe20    493b6610    cmp rsp, qword ptr [r14+0x10]
    receiver.go:15  0x46fe24    7624        jbe 0x46fe4a
    receiver.go:15  0x46fe26    55      push rbp
    receiver.go:15  0x46fe27    4889e5      mov rbp, rsp
    receiver.go:15  0x46fe2a    4883ec18    sub rsp, 0x18
    receiver.go:15  0x46fe2e    4889442428  mov qword ptr [rsp+0x28], rax
(dlv) disass
TEXT main.Read[go.shape.struct {}](SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:15  0x46fe20    493b6610    cmp rsp, qword ptr [r14+0x10]
    receiver.go:15  0x46fe24    7624        jbe 0x46fe4a
    receiver.go:15  0x46fe26    55      push rbp
    receiver.go:15  0x46fe27    4889e5      mov rbp, rsp
    receiver.go:15  0x46fe2a    4883ec18    sub rsp, 0x18
    receiver.go:15  0x46fe2e    4889442428  mov qword ptr [rsp+0x28], rax
    receiver.go:16  0x46fe33    8400        test byte ptr [rax], al
    receiver.go:16  0x46fe35    488b30      mov rsi, qword ptr [rax]
    receiver.go:16  0x46fe38    31db        xor ebx, ebx
    receiver.go:16  0x46fe3a    4889d9      mov rcx, rbx
    receiver.go:16  0x46fe3d    4889c2      mov rdx, rax
    receiver.go:16  0x46fe40    31c0        xor eax, eax
    receiver.go:16  0x46fe42    ffd6        call rsi
    receiver.go:17  0x46fe44    4883c418    add rsp, 0x18
    receiver.go:17  0x46fe48    5d      pop rbp
    receiver.go:17  0x46fe49    c3      ret
    receiver.go:15  0x46fe4a    4889442408  mov qword ptr [rsp+0x8], rax
    receiver.go:15  0x46fe4f    e8cc96ffff  call $runtime.morestack_noctxt
    receiver.go:15  0x46fe54    488b442408  mov rax, qword ptr [rsp+0x8]
    receiver.go:15  0x46fe59    ebc5        jmp $main.Read[go.shape.struct {}]
(dlv) b *0x46fe42
Breakpoint 3 set at 0x46fe42 for main.Read() ./receiver.go:16
(dlv) c
> [Breakpoint 3] main.Read[go.shape.struct {}]() ./receiver.go:16 (hits goroutine(1):1 total:1) (PC: 0x46fe42)
    11:     return len(p), nil
    12: }
    13:
    14: //go:noinline
    15: func Read[T io.Reader](r T) {
=>  16:     r.Read(nil)
    17: }
    18:
    19: func main() {
    20:     f := foo[string]{}
    21:     Read(f)
(dlv) disass
TEXT main.Read[go.shape.struct {}](SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
    receiver.go:15  0x46fe20    493b6610    cmp rsp, qword ptr [r14+0x10]
    receiver.go:15  0x46fe24    7624        jbe 0x46fe4a
    receiver.go:15  0x46fe26    55      push rbp
    receiver.go:15  0x46fe27    4889e5      mov rbp, rsp
    receiver.go:15  0x46fe2a    4883ec18    sub rsp, 0x18
    receiver.go:15  0x46fe2e    4889442428  mov qword ptr [rsp+0x28], rax
    receiver.go:16  0x46fe33    8400        test byte ptr [rax], al
    receiver.go:16  0x46fe35    488b30      mov rsi, qword ptr [rax]
    receiver.go:16  0x46fe38    31db        xor ebx, ebx
    receiver.go:16  0x46fe3a    4889d9      mov rcx, rbx
    receiver.go:16  0x46fe3d    4889c2      mov rdx, rax
    receiver.go:16  0x46fe40    31c0        xor eax, eax
=>  receiver.go:16  0x46fe42*   ffd6        call rsi
    receiver.go:17  0x46fe44    4883c418    add rsp, 0x18
    receiver.go:17  0x46fe48    5d      pop rbp
    receiver.go:17  0x46fe49    c3      ret
    receiver.go:15  0x46fe4a    4889442408  mov qword ptr [rsp+0x8], rax
    receiver.go:15  0x46fe4f    e8cc96ffff  call $runtime.morestack_noctxt
    receiver.go:15  0x46fe54    488b442408  mov rax, qword ptr [rsp+0x8]
    receiver.go:15  0x46fe59    ebc5        jmp $main.Read[go.shape.struct {}]
(dlv) si
> main.foo[string].Read() ./receiver.go:10 (PC: 0x46fe60)
TEXT main.foo[string].Read(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:10  0x46fe60    493b6610    cmp rsp, qword ptr [r14+0x10]
    receiver.go:10  0x46fe64    0f86a2000000    jbe 0x46ff0c
    receiver.go:10  0x46fe6a    55      push rbp
    receiver.go:10  0x46fe6b    4889e5      mov rbp, rsp
    receiver.go:10  0x46fe6e    4883ec68    sub rsp, 0x68
    receiver.go:10  0x46fe72    4d8b6620    mov r12, qword ptr [r14+0x20]
(dlv) disass
TEXT main.foo[string].Read(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:10  0x46fe60    493b6610        cmp rsp, qword ptr [r14+0x10]
    receiver.go:10  0x46fe64    0f86a2000000        jbe 0x46ff0c
    receiver.go:10  0x46fe6a    55          push rbp
    receiver.go:10  0x46fe6b    4889e5          mov rbp, rsp
    receiver.go:10  0x46fe6e    4883ec68        sub rsp, 0x68
    receiver.go:10  0x46fe72    4d8b6620        mov r12, qword ptr [r14+0x20]
    receiver.go:10  0x46fe76    4d85e4          test r12, r12
    receiver.go:10  0x46fe79    0f85ba000000        jnz 0x46ff39
    receiver.go:10  0x46fe7f    4889442478      mov qword ptr [rsp+0x78], rax
    receiver.go:10  0x46fe84    48899c2480000000    mov qword ptr [rsp+0x80], rbx
    receiver.go:10  0x46fe8c    48898c2488000000    mov qword ptr [rsp+0x88], rcx
    receiver.go:10  0x46fe94    48c744242000000000  mov qword ptr [rsp+0x20], 0x0
    receiver.go:10  0x46fe9d    440f117c2438        movups xmmword ptr [rsp+0x38], xmm15
    receiver.go:10  0x46fea3    440f117c2448        movups xmmword ptr [rsp+0x48], xmm15
    receiver.go:10  0x46fea9    488b5c2478      mov rbx, qword ptr [rsp+0x78]
    receiver.go:10  0x46feae    488b8c2480000000    mov rcx, qword ptr [rsp+0x80]
    receiver.go:10  0x46feb6    488bbc2488000000    mov rdi, qword ptr [rsp+0x88]
    receiver.go:10  0x46febe    488d05f3db0100      lea rax, ptr [rip+0x1dbf3]
    receiver.go:10  0x46fec5    e8f6feffff      call $main.foo[go.shape.string].Read
    receiver.go:10  0x46feca    4889442428      mov qword ptr [rsp+0x28], rax
    receiver.go:10  0x46fecf    48895c2448      mov qword ptr [rsp+0x48], rbx
    receiver.go:10  0x46fed4    48894c2450      mov qword ptr [rsp+0x50], rcx
    receiver.go:10  0x46fed9    488b542428      mov rdx, qword ptr [rsp+0x28]
    receiver.go:10  0x46fede    4889542430      mov qword ptr [rsp+0x30], rdx
    receiver.go:10  0x46fee3    48895c2458      mov qword ptr [rsp+0x58], rbx
    receiver.go:10  0x46fee8    48894c2460      mov qword ptr [rsp+0x60], rcx
    receiver.go:10  0x46feed    488b542430      mov rdx, qword ptr [rsp+0x30]
    receiver.go:10  0x46fef2    4889542420      mov qword ptr [rsp+0x20], rdx
    receiver.go:10  0x46fef7    48895c2438      mov qword ptr [rsp+0x38], rbx
    receiver.go:10  0x46fefc    48894c2440      mov qword ptr [rsp+0x40], rcx
    receiver.go:10  0x46ff01    488b442420      mov rax, qword ptr [rsp+0x20]
    receiver.go:10  0x46ff06    4883c468        add rsp, 0x68
    receiver.go:10  0x46ff0a    5d          pop rbp
    receiver.go:10  0x46ff0b    c3          ret
    receiver.go:10  0x46ff0c    4889442408      mov qword ptr [rsp+0x8], rax
    receiver.go:10  0x46ff11    48895c2410      mov qword ptr [rsp+0x10], rbx
    receiver.go:10  0x46ff16    48894c2418      mov qword ptr [rsp+0x18], rcx
    receiver.go:10  0x46ff1b    0f1f440000      nop dword ptr [rax+rax*1], eax
    receiver.go:10  0x46ff20    e8fb95ffff      call $runtime.morestack_noctxt
    receiver.go:10  0x46ff25    488b442408      mov rax, qword ptr [rsp+0x8]
    receiver.go:10  0x46ff2a    488b5c2410      mov rbx, qword ptr [rsp+0x10]
    receiver.go:10  0x46ff2f    488b4c2418      mov rcx, qword ptr [rsp+0x18]
    receiver.go:10  0x46ff34    e927ffffff      jmp $main.foo[string].Read
    receiver.go:10  0x46ff39    4c8d6c2478      lea r13, ptr [rsp+0x78]
    receiver.go:10  0x46ff3e    6690            data16 nop
    receiver.go:10  0x46ff40    4d392c24        cmp qword ptr [r12], r13
    receiver.go:10  0x46ff44    0f8535ffffff        jnz 0x46fe7f
    receiver.go:10  0x46ff4a    49892424        mov qword ptr [r12], rsp
    receiver.go:10  0x46ff4e    e92cffffff      jmp 0x46fe7f
(dlv) b *0x46fec5
Breakpoint 4 set at 0x46fec5 for main.foo.Read() ./receiver.go:10
(dlv) c
> [Breakpoint 4] main.foo[string].Read() ./receiver.go:10 (hits goroutine(1):1 total:1) (PC: 0x46fec5)
     5: )
     6:
     7: type foo[T string] struct{}
     8:
     9: //go:noinline
=>  10: func (f foo[T]) Read(p []byte) (n int, err error) {
    11:     return len(p), nil
    12: }
    13:
    14: //go:noinline
    15: func Read[T io.Reader](r T) {
(dlv) disass
TEXT main.foo[string].Read(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
    receiver.go:10  0x46fe60    493b6610        cmp rsp, qword ptr [r14+0x10]
    receiver.go:10  0x46fe64    0f86a2000000        jbe 0x46ff0c
    receiver.go:10  0x46fe6a    55          push rbp
    receiver.go:10  0x46fe6b    4889e5          mov rbp, rsp
    receiver.go:10  0x46fe6e    4883ec68        sub rsp, 0x68
    receiver.go:10  0x46fe72    4d8b6620        mov r12, qword ptr [r14+0x20]
    receiver.go:10  0x46fe76    4d85e4          test r12, r12
    receiver.go:10  0x46fe79    0f85ba000000        jnz 0x46ff39
    receiver.go:10  0x46fe7f    4889442478      mov qword ptr [rsp+0x78], rax
    receiver.go:10  0x46fe84    48899c2480000000    mov qword ptr [rsp+0x80], rbx
    receiver.go:10  0x46fe8c    48898c2488000000    mov qword ptr [rsp+0x88], rcx
    receiver.go:10  0x46fe94    48c744242000000000  mov qword ptr [rsp+0x20], 0x0
    receiver.go:10  0x46fe9d    440f117c2438        movups xmmword ptr [rsp+0x38], xmm15
    receiver.go:10  0x46fea3    440f117c2448        movups xmmword ptr [rsp+0x48], xmm15
    receiver.go:10  0x46fea9    488b5c2478      mov rbx, qword ptr [rsp+0x78]
    receiver.go:10  0x46feae    488b8c2480000000    mov rcx, qword ptr [rsp+0x80]
    receiver.go:10  0x46feb6    488bbc2488000000    mov rdi, qword ptr [rsp+0x88]
    receiver.go:10  0x46febe    488d05f3db0100      lea rax, ptr [rip+0x1dbf3]
=>  receiver.go:10  0x46fec5*   e8f6feffff      call $main.foo[go.shape.string].Read
    receiver.go:10  0x46feca    4889442428      mov qword ptr [rsp+0x28], rax
    receiver.go:10  0x46fecf    48895c2448      mov qword ptr [rsp+0x48], rbx
    receiver.go:10  0x46fed4    48894c2450      mov qword ptr [rsp+0x50], rcx
    receiver.go:10  0x46fed9    488b542428      mov rdx, qword ptr [rsp+0x28]
    receiver.go:10  0x46fede    4889542430      mov qword ptr [rsp+0x30], rdx
    receiver.go:10  0x46fee3    48895c2458      mov qword ptr [rsp+0x58], rbx
    receiver.go:10  0x46fee8    48894c2460      mov qword ptr [rsp+0x60], rcx
    receiver.go:10  0x46feed    488b542430      mov rdx, qword ptr [rsp+0x30]
    receiver.go:10  0x46fef2    4889542420      mov qword ptr [rsp+0x20], rdx
    receiver.go:10  0x46fef7    48895c2438      mov qword ptr [rsp+0x38], rbx
    receiver.go:10  0x46fefc    48894c2440      mov qword ptr [rsp+0x40], rcx
    receiver.go:10  0x46ff01    488b442420      mov rax, qword ptr [rsp+0x20]
    receiver.go:10  0x46ff06    4883c468        add rsp, 0x68
    receiver.go:10  0x46ff0a    5d          pop rbp
    receiver.go:10  0x46ff0b    c3          ret
    receiver.go:10  0x46ff0c    4889442408      mov qword ptr [rsp+0x8], rax
    receiver.go:10  0x46ff11    48895c2410      mov qword ptr [rsp+0x10], rbx
    receiver.go:10  0x46ff16    48894c2418      mov qword ptr [rsp+0x18], rcx
    receiver.go:10  0x46ff1b    0f1f440000      nop dword ptr [rax+rax*1], eax
    receiver.go:10  0x46ff20    e8fb95ffff      call $runtime.morestack_noctxt
    receiver.go:10  0x46ff25    488b442408      mov rax, qword ptr [rsp+0x8]
    receiver.go:10  0x46ff2a    488b5c2410      mov rbx, qword ptr [rsp+0x10]
    receiver.go:10  0x46ff2f    488b4c2418      mov rcx, qword ptr [rsp+0x18]
    receiver.go:10  0x46ff34    e927ffffff      jmp $main.foo[string].Read
    receiver.go:10  0x46ff39    4c8d6c2478      lea r13, ptr [rsp+0x78]
    receiver.go:10  0x46ff3e    6690            data16 nop
    receiver.go:10  0x46ff40    4d392c24        cmp qword ptr [r12], r13
    receiver.go:10  0x46ff44    0f8535ffffff        jnz 0x46fe7f
    receiver.go:10  0x46ff4a    49892424        mov qword ptr [r12], rsp
    receiver.go:10  0x46ff4e    e92cffffff      jmp 0x46fe7f
(dlv) p %x RAX
48dab8
(dlv) x -fmt hex -count 4 -size 8 0x48dab8
0x48dab8:   0x0000000000478ee0   0x3eb0000000000000   0x3f50624dd2f1a9fc   0x3f847ae147ae147b
(dlv) p *(*abi.Type)(0x478ee0)
internal/abi.Type {Size_: 0, PtrBytes: 0, Hash: 2310965435, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed|TFlagRegularMemory (15), Align_: 1, FieldAlign_: 1, Kind_: internal/reflectlite.Struct (25), Equal: runtime.memequal0, GCData: *0, Str: 9507, PtrToThis: 31808}
(dlv) si
> main.foo[go.shape.string].Read() ./receiver.go:10 (PC: 0x46fdc0)
TEXT main.foo[go.shape.string].Read(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:10  0x46fdc0    55      push rbp
    receiver.go:10  0x46fdc1    4889e5      mov rbp, rsp
    receiver.go:10  0x46fdc4    4883ec20    sub rsp, 0x20
    receiver.go:10  0x46fdc8    4889442430  mov qword ptr [rsp+0x30], rax
    receiver.go:10  0x46fdcd    48895c2438  mov qword ptr [rsp+0x38], rbx
    receiver.go:10  0x46fdd2    48894c2440  mov qword ptr [rsp+0x40], rcx
(dlv) disass
TEXT main.foo[go.shape.string].Read(SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:10  0x46fdc0    55          push rbp
    receiver.go:10  0x46fdc1    4889e5          mov rbp, rsp
    receiver.go:10  0x46fdc4    4883ec20        sub rsp, 0x20
    receiver.go:10  0x46fdc8    4889442430      mov qword ptr [rsp+0x30], rax
    receiver.go:10  0x46fdcd    48895c2438      mov qword ptr [rsp+0x38], rbx
    receiver.go:10  0x46fdd2    48894c2440      mov qword ptr [rsp+0x40], rcx
    receiver.go:10  0x46fdd7    48897c2448      mov qword ptr [rsp+0x48], rdi
    receiver.go:10  0x46fddc    48c7042400000000    mov qword ptr [rsp], 0x0
    receiver.go:10  0x46fde4    440f117c2410        movups xmmword ptr [rsp+0x10], xmm15
    receiver.go:11  0x46fdea    488b542440      mov rdx, qword ptr [rsp+0x40]
    receiver.go:11  0x46fdef    4889542408      mov qword ptr [rsp+0x8], rdx
    receiver.go:11  0x46fdf4    48891424        mov qword ptr [rsp], rdx
    receiver.go:11  0x46fdf8    440f117c2410        movups xmmword ptr [rsp+0x10], xmm15
    receiver.go:11  0x46fdfe    488b0424        mov rax, qword ptr [rsp]
    receiver.go:11  0x46fe02    31db            xor ebx, ebx
    receiver.go:11  0x46fe04    31c9            xor ecx, ecx
    receiver.go:11  0x46fe06    4883c420        add rsp, 0x20
    receiver.go:11  0x46fe0a    5d          pop rbp
    receiver.go:11  0x46fe0b    c3          ret
(dlv)

重要部分的解释:

(dlv) p %x RAX
48dba0
(dlv) x -fmt hex -count 4 -size 8 0x48dba0
0x48dba0:   0x000000000046fe60   0x0000000000478ee0   0x00000000004899bc   0x0000000000000045
(dlv) p *(*abi.Type)(0x478ee0)
internal/abi.Type {Size_: 0, PtrBytes: 0, Hash: 2310965435, TFlag: TFlagUncommon|TFlagExtraStar|TFlagNamed|TFlagRegularMemory (15), Align_: 1, FieldAlign_: 1, Kind_: internal/reflectlite.Struct (25), Equal: runtime.memequal0, GCData: *0, Str: 9507, PtrToThis: 31808}

这里的RAX寄存器的数据就是dict的指针,查看这个指针对应的内存数据,可以在后面对应到如下信息:

  • 0x46fe60代表子函数内部下一步调用的方法指针。
  • 0x478ee0代表泛型类型的类型元数据指针。
TEXT main.Read[go.shape.struct {}](SB) /root/project/go/src/github.com/qshuai/test/generic/receiver.go
=>  receiver.go:15  0x46fe20    493b6610    cmp rsp, qword ptr [r14+0x10]
    
    receiver.go:15  0x46fe2e    4889442428  mov qword ptr [rsp+0x28], rax
    receiver.go:16  0x46fe33    8400        test byte ptr [rax], al
    receiver.go:16  0x46fe35    488b30      mov rsi, qword ptr [rax]
    receiver.go:16  0x46fe38    31db        xor ebx, ebx
    receiver.go:16  0x46fe3a    4889d9      mov rcx, rbx
    receiver.go:16  0x46fe3d    4889c2      mov rdx, rax
    receiver.go:16  0x46fe40    31c0        xor eax, eax
    receiver.go:16  0x46fe42    ffd6        call rsi

上面指令中mov rsi, qword ptr [rax]将dict数据的第一个8字节赋值给了RSI寄存器,而这个寄存器的值(方法指针)就是下一步调用的地址。
RDX寄存器存储的是dict数据的指针,不过后面没有直接使用这个数据了。
call rsi根据dict中的信息直接调用指定泛型类型定义的方法。这个应该就是虚拟方法表的具体用处。也就是说泛型类型的方法调用不是通过在编译时直接指定的,而是通过一个字典数据指示进行跳转的。

    receiver.go:10  0x46febe    488d05f3db0100      lea rax, ptr [rip+0x1dbf3]
=>  receiver.go:10  0x46fec5*   e8f6feffff          call $main.foo[go.shape.string].Read

其中RAX寄存器中的值仍然为dict的地址,不过不是通过上面RDX寄存器的数据传递的,而是编译器写死的地址偏移量。然后调用$main.foo[go.shape.string].Read,这个地方相比于非泛型类型的方法调用,多了一步包装方法的调用,而这个包装方法是编译器自动生成的。

调用链路:

main.main -> 
              main.Read[go.shape.struct {}] -> 
                                                main.foo[string].Read -> 
                                                                           main.foo[go.shape.string].Read

这种场景下会存在两个字段信息:

$ go tool nm receiver | grep dict
  497420 R main..dict.Read[main.foo[string]]
  497330 R main..dict.foo[string]

那么根据上面的分析,这种场景下dict的数据结构如下:
main..dict.Read[main.foo[string]]:

type main..dict.Read[main.foo[string]] struct {
    Read uintptr
    T1   *abi.Type
}

其中Fn1是虚拟方法表实现的最好数据支撑。T1相当于参考文档中提到的子字典(Subdictionaries)。

main..dict.foo[string]:

type main..dict.foo[string] struct {
    T1 *abi.Type
}
接口中定义了多个方法,且均被调用的情况:
image.png

那么对应的dict结构如下:

type main..dict.ReadWrite[main.foo[string]] {
    Read  uintptr
    Write uintptr
    T1    *abi.Type
}
接口中定义了多个方法,方法调用顺序和定义顺序不同的情况:
image.png

和上个例子中的方法指针的顺序也调整了,Write方法指针在前,Read方法指针在后。这说明方法指针的顺序和调用顺序相关,而和接口声明顺序无关。
对应的dict结构如下:

type main..dict.ReadWrite[main.foo[string]] struct{
    Write uintptr
    Read  uintptr
    T1    *abi.Type
}
接口定义了多个方法,其中一个方法被泛型函数调用多次的情况:
image.png

看来这种情况下,方法指针也并不会多次出现,而是按照方法调用顺序去重后进行排列,那么此时的dict结构体如下:

type main..dict.ReadWrite[main.foo[string]] struct{
    Read uintptr
    Write uintptr
    T1   *abi.Type
}

参考文档:

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

推荐阅读更多精彩内容