GO interface

GO 语言 接口

interface

// 16 bytes on 64bit arch
type iface struct {
	tab  *itab
	data unsafe.Pointer
}
// https://github.com/golang/go/blob/bf86aec25972f3a100c3aa58a6abcbcc35bdea49/src/runtime/runtime2.go#L143-L146

tab 是 itab 对象的地址, 用来描述interface的类型和它指向的数据类型的数据结构。
data 指向interface持有的具体的数据。

每次创建接口,都会发生堆上的内存分配。

itab 结构:

// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.
type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
// https://github.com/golang/go/blob/bf86aec25972f3a100c3aa58a6abcbcc35bdea49/src/runtime/runtime2.go#L648-L658

itab 内嵌了 _type, _type 这个类型是runtime对任意GO语言类型的内部表示。
_type 类型 描述了一个“类型”的每一方面: 名字,特性,行为 等。
在这个例子中,_type字段描述了interface所持有的值的类型,也就是data指针所指向的值的类型。

_type 结构

// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/gc/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        *typeAlg
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}
// https://github.com/golang/go/blob/bf86aec25972f3a100c3aa58a6abcbcc35bdea49/src/runtime/type.go#L25-L43

nameOfftypeOff 类型是int32, 这两个值是链接器负责嵌入的,相对于可执行文件的元信息的偏移量。元信息会在运行期加载到runtime.moduledata结构体中。
https://github.com/golang/go/blob/bf86aec25972f3a100c3aa58a6abcbcc35bdea49/src/runtime/symtab.go#L352-L393
runtime提供了一些helper函数,这些函数能够帮助找到moduledata的偏移量,比如

func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {}
func resolveTypeOff(ptrInModule unsafe.Pointer, off typeOff) *_type {}

假设t是_type的话,只要调用 resolveTypeOff(t, t.ptrToThis) 就可以返回t的一份拷贝了。

interfacetype 结构体

// 80 bytes on a 64bit arch
type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

type imethod struct {
    name nameOff
    ityp typeOff
}

interfacetype 是 对于 _type的一种包装,在其顶部空间还包装了额外的interface相关的元信息。
展开iface结构

type iface struct {
	tab  *struct { // `itab`
		inter *struct {
				typ     struct {
							size       uintptr
							ptrdata    uintptr
							hash       uint32
							tflag      tflag
							align      uint8
							fieldalign uint8
							kind       uint8
							alg        *typeAlg
							gcdata    *byte
							str       nameOff
							ptrToThis typeOff
						}		
				pkgpath name
				mhdr    []imethod
			}
		
		_type *struct {
			size       uintptr
			ptrdata    uintptr // size of memory prefix holding all pointers
			hash       uint32
			tflag      tflag
			align      uint8
			fieldalign uint8
			kind       uint8
			alg        *typeAlg
			gcdata    *byte
			str       nameOff
			ptrToThis typeOff
		}

		hash  uint32
		_     [4]byte
		fun   [1]uintptr
	}

	data unsafe.Pointer
}

创建接口:
iface.go

type Mather interface {
	Add(a, b int32) int32
	Sub(a, b int64) int64
}

type Adder struct { id int32 }

//go:noinline
func (adder Adder) Add(a, b int32) int32 { return a + b }

//go:noinline
func (adder Adder) Sub(a, b int64) int64 { return a - b }

func main() {
	m := Mather(Adder{id: 6754})
	
	m.Add(10, 32)
}

剩余部分将演示一个持有T类型内容I类型的interface,即<I, T>.
比如Mather(Adder{id: 6754})就实例化了一个 iface<Mather, Adder>

;; part 1: allocate the receiver
0x001d MOVL	$6754, ""..autotmp_1+36(SP)
;; part 2: set up the itab
0x0025 LEAQ	go.itab."".Adder,"".Mather(SB), AX
0x002c MOVQ	AX, (SP)
;; part 3: set up the data
0x0030 LEAQ	""..autotmp_1+36(SP), AX
0x0035 MOVQ	AX, 8(SP)
0x003a CALL	runtime.convT2I32(SB)
0x003f MOVQ	16(SP), AX
0x0044 MOVQ	24(SP), CX

Part1:

MOVL		$6754, ""..autotmp_1+36(SP)

实例化Adder空间,Adder即Receiver.

Part2:

LEAQ 		go.itab."".Adder,"".Mather(SB), AX
MOVQ		AX, (SP)

看起来编译器已经创建了必要的itab来表示我们的iface<Mather, Adder> interface, 并以go.itab."".Adder,"".Mather(SB)供我们使用。

我们可以用伪代码来代替上面几行代码

tab := getSymAddr(`go.itab.main.Adder,main.Mather`).(*itab)

深入研究一下 go.itab."".Adder,"".Mather符号。
编译器-S 查看汇编

$ GOOS=linux GOARCH=amd64 go tool compile -S iface.go | grep -A 7 '^go.itab."".Adder,"".Mather'
go.itab."".Adder,"".Mather SRODATA dupok size=40
    0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    0x0010 8a 3d 5f 61 00 00 00 00 00 00 00 00 00 00 00 00  .=_a............
    0x0020 00 00 00 00 00 00 00 00                          ........
    rel 0+8 t=1 type."".Mather+0
    rel 8+8 t=1 type."".Adder+0
    rel 24+8 t=1 "".(*Adder).Add+0
    rel 32+8 t=1 "".(*Adder).Sub+0

第一句声明了符号和属性
go.itab."".Adder,"".Mather SRODATA dupok size=40
由于我们看的是编译器生成的间接目标文件,符号名还没有把package名字填充上。除此之外,我们的得到的是一个40字节的全局对象符号,该符号被存到二进制文件的.rodata段中。

40个字节的数据为

    0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    0x0010 8a 3d 5f 61 00 00 00 00 00 00 00 00 00 00 00 00  .=_a............
    0x0020 00 00 00 00 00 00 00 00                          ........
type itab struct { 				// 40 bytes on a 64bit arch
    inter *interfacetype 		// offset 0x00 ($00)
    _type *_type	 			// offset 0x08 ($08)
    hash  uint32	 			// offset 0x10 ($16)
    _     [4]byte	 				// offset 0x14 ($20)
    fun   [1]uintptr	 			// offset 0x18 ($24)
			 							// offset 0x20 ($32)
}

可以看到 8a 3d 5f 61hash uint32 相对应。
也就是说 main.Adder的hash值已经在我们的目标文件中了。

同时,还列出了提供给链接器重定向的相关指令:

    rel 0+8 t=1 type."".Mather+0
    rel 8+8 t=1 type."".Adder+0
    rel 24+8 t=1 "".(*Adder).Add+0
    rel 32+8 t=1 "".(*Adder).Sub+0

rel 0+8 t=1 type."".Mather+0 告诉链接器,将内容的前8个字节填充为 全局目标符号 type."".Mather 的地址。
rel 8+8 t=1 type."".Adder+0 告诉链接器,将内容的8~16个字节填充为 全局目标符号 type."".Adder 的地址。

一旦链接器完成了它的工作,执行完了这些指令, 40 字节后的序列化的 itab 就完成了。
我们再看的这些代码类似以下伪代码

tab := getSymAddr(`go.itab.main.Adder,main.Main.Mather`).(*itab)

tab.inter = getSymAddr(`type.main.Mather`).(*interfacetype)
tab._type = getSymAddr(`type.main.Adder`).(*_type)

tab.fun[0] = getSymAddr(`main.(*Adder).Add`).(uintptr)
tab.fun[1] = getSymAddr(`main.(*Adder).Sub`).(uintptr)

目前以前得到了一个完整可用的itab, 如果能再有一些相关的数据塞进去,就能得到一个完成的Interface了。

Part3: 分配数据

0x0030 LEAQ	""..autotmp_1+36(SP), AX			// 36(SP) 存储着Adder对象
0x0035 MOVQ	AX, 8(SP)								// 8(SP)存储该对象的地址
0x003a CALL	runtime.convT2I32(SB)
0x003f MOVQ	16(SP), AX
0x0044 MOVQ	24(SP), CX

Part1 中说过, 栈顶 (SP) 保存这 go.itab."".Adder."".Mather 的地址。同时Part2 我们在 36(SP)中存储一个十进制常量 $6754, 我们用 8(SP) 来将栈顶下方的该变量load到寄存器中。
所以 runtime.convT2I32 的参数1为 go.itab."".Adder."".Mather 的地址, 参数2为 &Adder

看函数地址 convT2I32

func convT2I32(tab *itab, elem unsafe.Pointer) (i iface) {
    t := tab._type
    /* ...omitted debug stuff... */
    var x unsafe.Pointer
    if *(*uint32)(elem) == 0 {
        x = unsafe.Pointer(&zeroVal[0])
    } else {
        x = mallocgc(4, t, false)
        *(*uint32)(x) = *(*uint32)(elem)
    }
    i.tab = tab
    i.data = x
    return
}

runtime.convT2I32 这个函数做了4件事

  1. 创建了一个iface的结构体 i.
  2. 将我们刚给i.tab 赋的值赋予了itab指针。
  3. 它在堆上分配了一个i.tab._type的新对象 i.tab._type, 然后将第二个参数elem指向的参数拷贝到这个新对象上。
  4. 返回interface.

现在我们已经完成了下面的这些工作(伪代码)

tab := getSymAddr(`go.itab.main.Adder,main.Mather`).(*itab)
elem := getSymAddr(`""..autotmp_1+36(SP)`).(*int32)

i := runtime.convTI32(tab, unsafe.Pointer(elem))

assert(i.tab == tab)
assert(*(*int32)(i.data) == 6754) // same value..
assert((*int32)(i.data) != elem)  // ..but different (al)locations!

这些代码都是 m := Mather(Adder{id: 6754}) 这一行代码生成的。
最终,我们得到了完成的可以工作的interface.

参考:
https://github.com/go-internals-cn/go-internals/blob/master/chapter2_interfaces/README.md#%E8%A7%A3%E6%9E%84%E6%8E%A5%E5%8F%A3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值