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
则为元数据指针的指针,那么解引用一次就会获得元数据指针的值,元数据指针的值就是元数据所在的内存地址。
- 我们知道泛型调用时第一个参数是dict的指针,而go1.16之后采用寄存器传参,第一个参数位于
AX
寄存器,所以这条指令就是为了将dict指针赋值给第一个参数:monomorphisation.go:4 0x46e36e 488d0523d90100 lea rax, ptr [rip+0x1d923]
- 赋值后,我们获取AX寄存器中的值,就是dict的指针数据:
p %x RAX
- 取指针指向数据的8个字节,我们猜测是
int
的类型元数据指针:x -fmt hex -count 1 -size 8 0x48bc98
- 我们将指针数据转化为类型元数据结构体:
p *(*abi.Type)(0x4747a0)
- 结果显示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
}
接口中定义了多个方法,且均被调用的情况:
那么对应的dict结构如下:
type main..dict.ReadWrite[main.foo[string]] {
Read uintptr
Write uintptr
T1 *abi.Type
}
接口中定义了多个方法,方法调用顺序和定义顺序不同的情况:
和上个例子中的方法指针的顺序也调整了,Write方法指针在前,Read方法指针在后。这说明方法指针的顺序和调用顺序相关,而和接口声明顺序无关。
对应的dict结构如下:
type main..dict.ReadWrite[main.foo[string]] struct{
Write uintptr
Read uintptr
T1 *abi.Type
}
接口定义了多个方法,其中一个方法被泛型函数调用多次的情况:
看来这种情况下,方法指针也并不会多次出现,而是按照方法调用顺序去重后进行排列,那么此时的dict结构体如下:
type main..dict.ReadWrite[main.foo[string]] struct{
Read uintptr
Write uintptr
T1 *abi.Type
}