Why為什么要研究dict的結構?
Go泛型實現并沒有對dict的數據結構進行統(tǒng)一的限制,而是針對不同的gcshape生成不同的dict數據,并存儲在只讀數據區(qū)。為了研究Go泛型實現中關于虛擬方法表的具體實現,需要弄清楚dict的內存數據到底是什么樣,已經如何通過dict進行方法跳轉的。
單態(tài)化:
單一泛型參數
代碼:
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個字節(jié),我們猜測是
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字節(jié)后即為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字節(jié)賦值給了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
}