深入interface原理

599 阅读7分钟

go version:1.17
macos

类型转换

指针类型

package main

import "fmt"

func main() {
	var g Game = &LOL{GameName: "LOL"}
	g.start("handsome boy")
	// 为了让 &LOL{GameName: "LOL"} 逃逸到内存
	fmt.Println(g)
}

type Game interface {
	start(string) string
	stop(string) string
}

type LOL struct {
	GameName string
}

func (l *LOL) start(name string) string {
	return fmt.Sprintf("%s start play %s", name, l.GameName)
}

func (l *LOL) stop(name string) string {
	return fmt.Sprintf("%s stop play %s", name, l.GameName)
}

查看汇编代码:
go tool compile -S -N -l main.go

"".main STEXT size=394 args=0x0 locals=0x90 funcid=0x0
	(...)
	0x0026 00038 (main.go:5)	FUNCDATA	$2, "".main.stkobj(SB)
	0x0026 00038 (main.go:6)	LEAQ	type."".LOL(SB), AX
	0x002d 00045 (main.go:6)	PCDATA	$1, $0
	0x002d 00045 (main.go:6)	CALL	runtime.newobject(SB)
	0x0032 00050 (main.go:6)	MOVQ	AX, ""..autotmp_3+48(SP)
	0x0037 00055 (main.go:6)	MOVQ	$3, 8(AX)
	0x003f 00063 (main.go:6)	PCDATA	$0, $-2
	0x003f 00063 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x0046 00070 (main.go:6)	JEQ	74
	0x0048 00072 (main.go:6)	JMP	86
	0x004a 00074 (main.go:6)	LEAQ	go.string."LOL"(SB), DX
	0x0051 00081 (main.go:6)	MOVQ	DX, (AX)
	0x0054 00084 (main.go:6)	JMP	103
	0x0056 00086 (main.go:6)	MOVQ	AX, DI
	0x0059 00089 (main.go:6)	LEAQ	go.string."LOL"(SB), DX
	0x0060 00096 (main.go:6)	CALL	runtime.gcWriteBarrierDX(SB)
	0x0065 00101 (main.go:6)	JMP	103
	0x0067 00103 (main.go:6)	PCDATA	$0, $-1
	0x0067 00103 (main.go:6)	MOVQ	""..autotmp_3+48(SP), DX
	0x006c 00108 (main.go:6)	MOVQ	DX, ""..autotmp_1+56(SP)
	0x0071 00113 (main.go:6)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI
	0x0078 00120 (main.go:6)	MOVQ	SI, "".g+64(SP)
	0x007d 00125 (main.go:6)	MOVQ	DX, "".g+72(SP)
	0x0082 00130 (main.go:7)	MOVQ	"".g+64(SP), AX
	0x0087 00135 (main.go:7)	MOVQ	"".g+72(SP), DX
	0x008c 00140 (main.go:7)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI
	0x0093 00147 (main.go:7)	CMPQ	AX, SI
	0x0096 00150 (main.go:7)	JEQ	157
	0x0098 00152 (main.go:7)	JMP	359
	0x009d 00157 (main.go:7)	MOVQ	DX, ""..autotmp_4+40(SP)
	0x00a2 00162 (main.go:7)	MOVQ	DX, AX
	0x00a5 00165 (main.go:7)	LEAQ	go.string."handsome boy"(SB), BX
	0x00ac 00172 (main.go:7)	MOVL	$12, CX
	0x00b1 00177 (main.go:7)	PCDATA	$1, $1
	0x00b1 00177 (main.go:7)	CALL	"".(*LOL).start(SB)
        (...)

接下俩分段解读以上代码.

0x0026 00038 (main.go:6)	LEAQ	type."".LOL(SB), AX  // 将type."".LOL地址放入AX中
0x002d 00045 (main.go:6)	PCDATA	$1, $0
0x002d 00045 (main.go:6)	CALL	runtime.newobject(SB) //AX=&LOL AX作为参数,调用newobject,创建的LOL对象的地址放入AX中
0x0032 00050 (main.go:6)	MOVQ	AX, ""..autotmp_3+48(SP) //48(SP)=AX 将LOL放入栈中
0x0037 00055 (main.go:6)	MOVQ	$3, 8(AX) //(AX.Name).Len = 3
0x003f 00063 (main.go:6)	PCDATA	$0, $-2
0x003f 00063 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0 // 比较两者
0x0046 00070 (main.go:6)	JEQ	74  //相等则跳转到(0x004a) 其实会走到这里
0x0048 00072 (main.go:6)	JMP	86  //不相等则跳转到(0x0056)
0x004a 00074 (main.go:6)	LEAQ	go.string."LOL"(SB), DX // DX= &"LOL"
0x0051 00081 (main.go:6)	MOVQ	DX, (AX)   //(AX.Name).Data = DX = &"LOL"
0x0054 00084 (main.go:6)	JMP	103    // 跳转到(0x0067)

func newobject(typ *_type) unsafe.Pointer {
  return mallocgc(typ.size, typ, true)
}

*_type放入AX中,AX作为参数传递给newobject,创建LOL对象,对象的地址又放入AX中。

image.png

0x0067 00103 (main.go:6)	PCDATA	$0, $-1
	0x0067 00103 (main.go:6)	MOVQ	""..autotmp_3+48(SP), DX // DX=48(SP)
	0x006c 00108 (main.go:6)	MOVQ	DX, ""..autotmp_1+56(SP) //56(SP)=DX
	0x0071 00113 (main.go:6)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI // SI = *itab(LOL)
	0x0078 00120 (main.go:6)	MOVQ	SI, "".g+64(SP)  // 64(SP)=SI
	0x007d 00125 (main.go:6)	MOVQ	DX, "".g+72(SP)  // 72(SP)=DX
	0x0082 00130 (main.go:7)	MOVQ	"".g+64(SP), AX  // AX=64(SP)
	0x0087 00135 (main.go:7)	MOVQ	"".g+72(SP), DX  // DX=72(SP)
	0x008c 00140 (main.go:7)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI //SI = *itab(LOL)
	0x0093 00147 (main.go:7)	CMPQ	AX, SI   //比较下
	0x0096 00150 (main.go:7)	JEQ	157  //跳转到(0x009d)
	0x0098 00152 (main.go:7)	JMP	359
	0x009d 00157 (main.go:7)	MOVQ	DX, ""..autotmp_4+40(SP) //40(SP)=DX
	0x00a2 00162 (main.go:7)	MOVQ	DX, AX // AX=DX=*itab 作为第一个参数
	0x00a5 00165 (main.go:7)	LEAQ	go.string."handsome boy"(SB), BX //BX=&"handsome boy" 作为第二个参数
	0x00ac 00172 (main.go:7)	MOVL	$12, CX // CX=$12,作为第三个参数
	0x00b1 00177 (main.go:7)	PCDATA	$1, $1
	0x00b1 00177 (main.go:7)	CALL	"".(*LOL).start(SB) //调用函数

image.png

64(SP)~80(SP)共同组成了runtime.iface结构体

结构体类型

"".main STEXT size=435 args=0x0 locals=0xb8 funcid=0x0
	(...)
	0x002f 00047 (main.go:6)	LEAQ	go.string."LOL"(SB), CX // CX=&"LOL"
	0x0036 00054 (main.go:6)	MOVQ	CX, ""..autotmp_1+136(SP) // 136(SP)=&"LOL
	0x003e 00062 (main.go:6)	MOVQ	$3, ""..autotmp_1+144(SP) //144(SP)=3
	0x004a 00074 (main.go:6)	MOVQ	CX, ""..autotmp_3+104(SP) //104(SP)=&"LOL"
	0x004f 00079 (main.go:6)	MOVQ	$3, ""..autotmp_3+112(SP) //112(SP)=3
	0x0058 00088 (main.go:6)	LEAQ	""..autotmp_3+104(SP), CX // CX=&104(SP)
	0x005d 00093 (main.go:6)	TESTB	AL, (CX)
	0x005f 00095 (main.go:6)	MOVQ	""..autotmp_3+104(SP), AX // AX=104(SP)=&"LOL"
	0x0064 00100 (main.go:6)	MOVQ	AX, ""..autotmp_4+88(SP) //88(SP)=AX
	0x0069 00105 (main.go:6)	MOVQ	$3, ""..autotmp_4+96(SP) //96(SP)=3
	0x0072 00114 (main.go:6)	MOVL	$3, BX //BX=3
	0x0077 00119 (main.go:6)	PCDATA	$1, $0
	0x0077 00119 (main.go:6)	CALL	runtime.convTstring(SB) // AX,BX为参数
	0x007c 00124 (main.go:6)	MOVQ	AX, ""..autotmp_5+48(SP) //48(SP)=&StringHeader{}
	0x0081 00129 (main.go:6)	LEAQ	go.itab."".LOL,"".Game(SB), CX //CX=*itab
	0x0088 00136 (main.go:6)	MOVQ	CX, "".g+56(SP) //56(SP)=CX=*itab
	0x008d 00141 (main.go:6)	MOVQ	AX, "".g+64(SP) //64(SP)=AX=&StringHeader{}
	0x0092 00146 (main.go:7)	MOVUPS	X15, ""..autotmp_1+136(SP)
	0x009b 00155 (main.go:7)	MOVQ	"".g+56(SP), AX //AX=56(SP)=*itab
	0x00a0 00160 (main.go:7)	MOVQ	"".g+64(SP), CX //CX=64(SP)=&StringHeader{}
	0x00a5 00165 (main.go:7)	LEAQ	go.itab."".LOL,"".Game(SB), DX //DX=*itab
	0x00ac 00172 (main.go:7)	CMPQ	AX, DX
	0x00af 00175 (main.go:7)	JEQ	182
	0x00b1 00177 (main.go:7)	JMP	405
	0x00b6 00182 (main.go:7)	MOVQ	(CX), AX //AX=*CX="LOL"
	0x00b9 00185 (main.go:7)	MOVQ	8(CX), BX //BX=*CX.Len=3
	0x00bd 00189 (main.go:7)	MOVQ	AX, ""..autotmp_1+136(SP)
	0x00c5 00197 (main.go:7)	MOVQ	BX, ""..autotmp_1+144(SP)
	0x00cd 00205 (main.go:7)	LEAQ	go.string."handsome boy"(SB), CX // CX=&"handsome boy"
	0x00d4 00212 (main.go:7)	MOVL	$12, DI
	0x00d9 00217 (main.go:7)	PCDATA	$1, $1
	0x00d9 00217 (main.go:7)	CALL	"".LOL.start(SB)
	(...)
func convTstring(val string) (x unsafe.Pointer) {
   if val == "" {
      x = unsafe.Pointer(&zeroVal[0])
   } else {
      x = mallocgc(unsafe.Sizeof(val), stringType, true)
      *(*string)(x) = val
   }
   return
}

convTstringAXBX取参数,生成一个字符串变量,返回他的指针放在AX上,并拷贝到48(SP)上,后面的指令将*itab放到56(SP)上,*string放到64(SP)上,所以56(SP)64(SP)组成了iface

image.png

类型断言

非空接口

非具体类型

采用如下代码来生成汇编代码

func main() {
	var s Game = &LOL{GameName: "halfrost"}
	v, ok := s.(Game)
        fmt.Println(s)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}
"".main STEXT size=727 args=0x0 locals=0x118 funcid=0x0
        (...)
	0x0029 00041 (main.go:6)	LEAQ	type."".LOL(SB), AX      //AX=*_type(LOL)
	0x0030 00048 (main.go:6)	PCDATA	$1, $0  
	0x0030 00048 (main.go:6)	CALL	runtime.newobject(SB)    //AX作为参数,调用runtime.newobject
	0x0035 00053 (main.go:6)	MOVQ	AX, ""..autotmp_8+48(SP) //SP(48)=*LOL
	0x003a 00058 (main.go:6)	MOVQ	$8, 8(AX)                //(AX.Name).Len=8
	0x0042 00066 (main.go:6)	PCDATA	$0, $-2
	0x0042 00066 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x0049 00073 (main.go:6)	JEQ	77     // goto 0x004d
	0x004b 00075 (main.go:6)	JMP	89
	0x004d 00077 (main.go:6)	LEAQ	go.string."halfrost"(SB), DX // DX=&"halfrost"
	0x0054 00084 (main.go:6)	MOVQ	DX, (AX)                 //(AX.Name).Data=DX=&"halfrost"
	0x0057 00087 (main.go:6)	JMP	106
	0x0059 00089 (main.go:6)	MOVQ	AX, DI                   
	0x005c 00092 (main.go:6)	LEAQ	go.string."halfrost"(SB), DX
	0x0063 00099 (main.go:6)	CALL	runtime.gcWriteBarrierDX(SB)
	0x0068 00104 (main.go:6)	JMP	106
	// jump 106
	0x006a 00106 (main.go:6)	PCDATA	$0, $-1 
	0x006a 00106 (main.go:6)	MOVQ	""..autotmp_8+48(SP), DX  //DX=48(SP)
	0x006f 00111 (main.go:6)	MOVQ	DX, ""..autotmp_3+56(SP)  //56(SP)=DX
	0x0074 00116 (main.go:6)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI //SI=*itab
	0x007b 00123 (main.go:6)	MOVQ	SI, "".s+112(SP)   //112(SP)=SI
	0x0080 00128 (main.go:6)	MOVQ	DX, "".s+120(SP)   //120(SP)=DX
	0x0085 00133 (main.go:7)	MOVUPS	X15, ""..autotmp_4+144(SP)
	0x008e 00142 (main.go:7)	MOVQ	"".s+112(SP), BX  //BX=112(SP)=*itab
	0x0093 00147 (main.go:7)	MOVQ	"".s+120(SP), CX  //CX=120(SP)=*LOL
	0x0098 00152 (main.go:7)	LEAQ	type."".Game(SB), AX // AX=*_type(Game)
	0x009f 00159 (main.go:7)	NOP
	0x00a0 00160 (main.go:7)	CALL	runtime.assertI2I2(SB)
	0x00a5 00165 (main.go:7)	MOVQ	AX, ""..autotmp_4+144(SP) //144(SP)=*itab
	0x00ad 00173 (main.go:7)	MOVQ	BX, ""..autotmp_4+152(SP) //152(SP)=&"halfrost"
	0x00b5 00181 (main.go:7)	TESTQ	AX, AX   //判断AX是否为空,这里不为空,ZF=0
	0x00b8 00184 (main.go:7)	SETNE	""..autotmp_5+47(SP)  //ZF=0,所以47(SP)=1
	0x00bd 00189 (main.go:7)	MOVQ	""..autotmp_4+144(SP), DX //DX=144(SP)=*itab
	0x00c5 00197 (main.go:7)	MOVQ	""..autotmp_4+152(SP), SI //SI=152(SP)=*LOL
	0x00cd 00205 (main.go:7)	MOVQ	DX, "".v+96(SP)   //96(SP)=*itab
	0x00d2 00210 (main.go:7)	MOVQ	SI, "".v+104(SP)  //104(SP)=*LOL
	0x00d7 00215 (main.go:7)	MOVBLZX	""..autotmp_5+47(SP), DX //MOVBLZX移动一字节 DX=1
	0x00dc 00220 (main.go:7)	MOVB	DL, "".ok+46(SP)   // 只用DL的低8位
	0x00e0 00224 (main.go:8)	MOVBLZX	"".ok+46(SP), DX
	0x00e5 00229 (main.go:8)	MOVB	DL, ""..autotmp_5+47(SP)
        (...)

image.png

分析下的assertI2I2调用

func assertI2I2(inter *interfacetype, i iface) (r iface) {
   tab := i.tab
   if tab == nil {
      return
   }
   if tab.inter != inter {
      tab = getitab(inter, tab._type, true)
      if tab == nil {
         return
      }
   }
   r.tab = tab
   r.data = i.data
   return
}
0x008e 00142 (main.go:7)	MOVQ	"".s+112(SP), BX  //BX=112(SP)=*itab
0x0093 00147 (main.go:7)	MOVQ	"".s+120(SP), CX  //CX=120(SP)=*LOL
0x0098 00152 (main.go:7)	LEAQ	type."".Game(SB), AX // AX=*_type(Game)
0x009f 00159 (main.go:7)	NOP
0x00a0 00160 (main.go:7)	CALL	runtime.assertI2I2(SB)

inter参数为*_type(Game), ifaceBXCX组成,
所以assertI2I2可以简单简化为比较AXBX.tab.inner,若相同,则构造一个新的iface返回.

在go1.17中,assertI2I2去掉了bool返回值,那么ok这个值是这么得来的呢? 我们能看到一个在0x00b5有一个人TESTQ指令,意思是如果AX不为空,则ZF=0,而SETNE意思是如果ZF=0,则47(SP)=1(true)

而在go1.16中,assertI2I2的函数是下面这样的

func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) 

所以不需要向go1.17那样利用TESTQSETNE得到返回值

具体类型

"".main STEXT size=682 args=0x0 locals=0x108 funcid=0x0
        (...)
	0x0029 00041 (main.go:6)	LEAQ	type."".LOL(SB), AX  //AX=&"LOL
	0x0030 00048 (main.go:6)	PCDATA	$1, $0
	0x0030 00048 (main.go:6)	CALL	runtime.newobject(SB)
	0x0035 00053 (main.go:6)	MOVQ	AX, ""..autotmp_7+56(SP) //56(SP)=*LOL
	0x003a 00058 (main.go:6)	MOVQ	$8, 8(AX)   //(AX.Name).Len=8
	0x0042 00066 (main.go:6)	PCDATA	$0, $-2
	0x0042 00066 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x0049 00073 (main.go:6)	JEQ	77  // goto 0x004d
	0x004b 00075 (main.go:6)	JMP	89  // goto 0x0059
	// JEQ	77
	0x004d 00077 (main.go:6)	LEAQ	go.string."halfrost"(SB), DX // (AX.Name).Data=&"halfrost"
	0x0054 00084 (main.go:6)	MOVQ	DX, (AX) 
	0x0057 00087 (main.go:6)	JMP	106 // goto 0x006a
	// JMP	89
	0x0059 00089 (main.go:6)	MOVQ	AX, DI
	0x005c 00092 (main.go:6)	LEAQ	go.string."halfrost"(SB), DX
	0x0063 00099 (main.go:6)	CALL	runtime.gcWriteBarrierDX(SB)
	0x0068 00104 (main.go:6)	JMP	106
	// JMP	106
	0x006a 00106 (main.go:6)	PCDATA	$0, $-1
	0x006a 00106 (main.go:6)	MOVQ	""..autotmp_7+56(SP), DX //DX=56(SP)
	0x006f 00111 (main.go:6)	MOVQ	DX, ""..autotmp_3+64(SP) 64(SP)=DX
	0x0074 00116 (main.go:6)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI //SI=*itab
	0x007b 00123 (main.go:6)	MOVQ	SI, "".s+120(SP)    // 120(SP)=SI=*itab
	0x0080 00128 (main.go:6)	MOVQ	DX, "".s+128(SP)    // 128(SP)=DX=56(SP)=*LOL
	0x0088 00136 (main.go:7)	MOVUPS	X15, ""..autotmp_4+152(SP)
	0x0091 00145 (main.go:7)	MOVQ	"".s+128(SP), DX //DX=128(SP)
	0x0099 00153 (main.go:7)	LEAQ	go.itab."".LOL,"".Game(SB), SI // SI=*itab
	0x00a0 00160 (main.go:7)	CMPQ	"".s+120(SP), SI //比较120(SP)处的*itab和刚刚构造出的*itab是否相等
	0x00a5 00165 (main.go:7)	JEQ	169  // goto 0x00a9
	0x00a7 00167 (main.go:7)	JMP	183  // goto 0x00b7
	// === JEQ	169 
	0x00a9 00169 (main.go:7)	MOVQ	(DX), SI //SI=LOL{}
	0x00ac 00172 (main.go:7)	MOVQ	8(DX), DX //DX=8
	0x00b0 00176 (main.go:7)	MOVL	$1, AX //AX=1
	0x00b5 00181 (main.go:7)	JMP	191 // goto 0x00bf
	// === JMP	183 
	0x00b7 00183 (main.go:7)	XORL	DX, DX
	0x00b9 00185 (main.go:7)	XORL	AX, AX
	0x00bb 00187 (main.go:7)	XORL	SI, SI
	0x00bd 00189 (main.go:7)	JMP	191
	// === JMP	191
	0x00bf 00191 (main.go:7)	MOVQ	SI, ""..autotmp_4+152(SP)  // 152(SP)=LOL{}
	0x00c7 00199 (main.go:7)	MOVQ	DX, ""..autotmp_4+160(SP) //160S(SP)=8
	0x00cf 00207 (main.go:7)	MOVB	AL, ""..autotmp_5+47(SP)  //47=1(true)
	0x00d3 00211 (main.go:7)	MOVQ	""..autotmp_4+152(SP), DX
	0x00db 00219 (main.go:7)	MOVQ	""..autotmp_4+160(SP), SI
	0x00e3 00227 (main.go:7)	MOVQ	DX, "".v+104(SP)                    
        (...)

具体类型不会调用runtime.assertI2I2,直接构造处iface,放在120(SP),再构造一个*itab120(SP)*itab比较

image.png

空接口

func main() {
	var s interface{} = &LOL{GameName: "GameName"}
	v, ok := s.(int)
	fmt.Println(v, ok)
	fmt.Println(s)
}
"".main STEXT size=534 args=0x0 locals=0xd0 funcid=0x0
        (...)
    	0x0026 00038 (main.go:5)	LEAQ	type."".LOL(SB), AX  //AX=*_type(LOL)
	0x002d 00045 (main.go:5)	PCDATA	$1, $0
	0x002d 00045 (main.go:5)	CALL	runtime.newobject(SB)
	0x0032 00050 (main.go:5)	MOVQ	AX, ""..autotmp_8+48(SP) //48(SP)=*LOL{}
	0x0037 00055 (main.go:5)	MOVQ	$8, 8(AX)    //(AX.Name).Len=8
	0x003f 00063 (main.go:5)	PCDATA	$0, $-2
	0x003f 00063 (main.go:5)	CMPL	runtime.writeBarrier(SB), $0
	0x0046 00070 (main.go:5)	JEQ	74
	0x0048 00072 (main.go:5)	JMP	86
	0x004a 00074 (main.go:5)	LEAQ	go.string."GameName"(SB), CX //CX=&"GameName"
	0x0051 00081 (main.go:5)	MOVQ	CX, (AX)  //(AX.Name).Data=&"GameName"
	0x0054 00084 (main.go:5)	JMP	103 //goto 0x0067
	0x0056 00086 (main.go:5)	MOVQ	AX, DI
	0x0059 00089 (main.go:5)	LEAQ	go.string."GameName"(SB), CX
	0x0060 00096 (main.go:5)	CALL	runtime.gcWriteBarrierCX(SB)
	0x0065 00101 (main.go:5)	JMP	103
	// === JMP	103
	0x0067 00103 (main.go:5)	PCDATA	$0, $-1
	0x0067 00103 (main.go:5)	MOVQ	""..autotmp_8+48(SP), CX //CX=48(SP)
	0x006c 00108 (main.go:5)	MOVQ	CX, ""..autotmp_3+56(SP) //56(SP)=CX
	0x0071 00113 (main.go:5)	LEAQ	type.*"".LOL(SB), DX //DX=*_type(LOL)
	0x0078 00120 (main.go:5)	MOVQ	DX, "".s+88(SP)  //88(SP)=DX
	0x007d 00125 (main.go:5)	MOVQ	CX, "".s+96(SP)  //96(SP)=CX
	0x0082 00130 (main.go:6)	MOVQ	"".s+96(SP), CX  //CX=96(SP)
	0x0087 00135 (main.go:6)	LEAQ	type.int(SB), DX //DX=*int
	0x008e 00142 (main.go:6)	CMPQ	"".s+88(SP), DX //比较 *_type(LOL)和*int
	0x0093 00147 (main.go:6)	JEQ	151  //相等 goto 0x0097
	0x0095 00149 (main.go:6)	JMP	162  // 不想等 goto 0x00a2
	// === JEQ	151
	0x0097 00151 (main.go:6)	MOVQ	(CX), CX //CX="GameName"
	0x009a 00154 (main.go:6)	MOVL	$1, AX   //AX=1(true)
	0x009f 00159 (main.go:6)	NOP
	0x00a0 00160 (main.go:6)	JMP	168
	// === JMP	162
	0x00a2 00162 (main.go:6)	XORL	CX, CX //CX=0
	0x00a4 00164 (main.go:6)	XORL	AX, AX //AX=0
	0x00a6 00166 (main.go:6)	JMP	168 // goto 0x00a8
	// === JMP	168
	0x00a8 00168 (main.go:6)	MOVQ	CX, ""..autotmp_4+40(SP)  //40(SP)="GameName"
	0x00ad 00173 (main.go:6)	MOVB	AL, ""..autotmp_5+31(SP)  //31(SP)=1
	0x00b1 00177 (main.go:6)	MOVQ	""..autotmp_4+40(SP), CX  //CX="GameName"
	0x00b6 00182 (main.go:6)	MOVQ	CX, "".v+32(SP)           //32(SP)="GameName"
	0x00bb 00187 (main.go:6)	MOVBLZX	""..autotmp_5+31(SP), CX  //CX=1 
	0x00c0 00192 (main.go:6)	MOVB	CL, "".ok+30(SP)  //30(SP)=1
	0x00c4 00196 (main.go:7)	MOVBLZX	"".ok+30(SP), CX  //CX=1
	0x00c9 00201 (main.go:7)	MOVB	CL, ""..autotmp_5+31(SP) //31(SP)=1
        (...)

image.png

类型查询

非空接口

用一下代码验证

func main() {
	var s Game = &LOL{GameName: "GameName"}
	switch s.(type) {
    case Game:
        g := s.(Game)
        g.start("game call start")
    case *LOL:
        l := s.(*LOL)
        l.start("*lol call start")

    case LOL:
        l := s.(LOL)
        l.start("lol call start")
    }
}

得到汇编代码

"".main STEXT size=709 args=0x0 locals=0xc8 funcid=0x0
	0x0026 00038 (main.go:6)	LEAQ	type."".LOL(SB), AX
	0x002d 00045 (main.go:6)	PCDATA	$1, $0
	0x002d 00045 (main.go:6)	CALL	runtime.newobject(SB)
	0x0032 00050 (main.go:6)	MOVQ	AX, ""..autotmp_7+56(SP)  // 56(SP)=&LOL{}
	0x0037 00055 (main.go:6)	MOVQ	$8, 8(AX)
	0x003f 00063 (main.go:6)	PCDATA	$0, $-2
	0x003f 00063 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x0046 00070 (main.go:6)	JEQ	74
	0x0048 00072 (main.go:6)	JMP	86
	0x004a 00074 (main.go:6)	LEAQ	go.string."GameName"(SB), DX
	0x0051 00081 (main.go:6)	MOVQ	DX, (AX)
	0x0054 00084 (main.go:6)	JMP	103  // goto  0x0067
	0x0056 00086 (main.go:6)	MOVQ	AX, DI
	0x0059 00089 (main.go:6)	LEAQ	go.string."GameName"(SB), DX
	0x0060 00096 (main.go:6)	CALL	runtime.gcWriteBarrierDX(SB)
	0x0065 00101 (main.go:6)	JMP	103
	// JMP	103
	0x0067 00103 (main.go:6)	PCDATA	$0, $-1
	0x0067 00103 (main.go:6)	MOVQ	""..autotmp_7+56(SP), DX // DX=&LOL{}
	0x006c 00108 (main.go:6)	MOVQ	DX, ""..autotmp_4+64(SP)  // 64(SP)=&LOL
	0x0071 00113 (main.go:6)	LEAQ	go.itab.*"".LOL,"".Game(SB), SI // SI=*itab
	0x0078 00120 (main.go:6)	MOVQ	SI, "".s+80(SP) // 80(SP)=*itab
	0x007d 00125 (main.go:6)	MOVQ	DX, "".s+88(SP)  // 88(SP)=&LOL
	0x0082 00130 (main.go:7)	MOVQ	"".s+80(SP), DX  // DX=*itab
	0x0087 00135 (main.go:7)	MOVQ	"".s+88(SP), SI  // SI=&LOL{}
	0x008c 00140 (main.go:7)	MOVQ	DX, ""..autotmp_8+128(SP)  // 128(SP)=DX=*itab
	0x0094 00148 (main.go:7)	MOVQ	SI, ""..autotmp_8+136(SP)  // 136(SP)=SI=&LOL{}
	0x009c 00156 (main.go:7)	NOP
	0x00a0 00160 (main.go:7)	TESTQ	DX, DX
	0x00a3 00163 (main.go:7)	JNE	170  // goto 0x00aa
	0x00a5 00165 (main.go:7)	JMP	635

到目前为止,函数栈分布如下:

image.png 接下来继续分析汇编代码:

0x00a0 00160 (main.go:7)	TESTQ	DX, DX
0x00a3 00163 (main.go:7)	JNE	170  // goto 0x00aa
0x00a5 00165 (main.go:7)	JMP	635  // goto 0x0145
// JNE	170 由上面代码块跳转过来
0x00aa 00170 (main.go:7)	MOVL	16(DX), DX //DX=hash
0x00ad 00173 (main.go:7)	MOVL	DX, ""..autotmp_10+44(SP) // 44(SP)=hash
0x00b1 00177 (main.go:8)	MOVQ	""..autotmp_8+128(SP), BX //BX=*itab
0x00b9 00185 (main.go:8)	MOVQ	""..autotmp_8+136(SP), CX //CX=&LOL{}
0x00c1 00193 (main.go:8)	LEAQ	type."".Game(SB), AX    //AX=*_type(Game)
0x00c8 00200 (main.go:8)	PCDATA	$1, $1
0x00c8 00200 (main.go:8)	CALL	runtime.assertI2I2(SB)  //call assertI2I2 AX为第一个参数,BX、CX共同组成第二个参数
0x00cd 00205 (main.go:8)	TESTQ	AX, AX  
0x00d0 00208 (main.go:8)	SETNE	""..autotmp_9+43(SP) //43(SP)=1  这里按照示例的代码,会匹配成功
0x00d5 00213 (main.go:8)	JNE	217 // goto 0x00d9
0x00d7 00215 (main.go:8)	JMP	325

我们先分析assertI2I2返回true的情况


// JNE	217 
0x00d9 00217 (main.go:8)	PCDATA	$1, $-1
0x00d9 00217 (main.go:8)	JMP	219 // goto 0x00db
// JMP	219
0x00db 00219 (main.go:9)	MOVUPS	X15, ""..autotmp_5+160(SP)
0x00e4 00228 (main.go:9)	MOVQ	"".s+88(SP), CX   //CX=&LOL{}
0x00e9 00233 (main.go:9)	MOVQ	CX, ""..autotmp_12+72(SP) //72=&LOL{}
0x00ee 00238 (main.go:9)	MOVQ	"".s+80(SP), BX // BX=*itab
0x00f3 00243 (main.go:9)	LEAQ	type."".Game(SB), AX  //AX=*_type(Game)
0x00fa 00250 (main.go:9)	PCDATA	$1, $2
0x00fa 00250 (main.go:9)	CALL	runtime.assertI2I(SB)  // call assertI2I
0x00ff 00255 (main.go:9)	MOVQ	AX, ""..autotmp_5+160(SP) //160(SP)=*itab
0x0107 00263 (main.go:9)	MOVQ	""..autotmp_12+72(SP), CX //CX=&LOL{}
// =======接下来就是正常的函数调用了
0x010c 00268 (main.go:9)	MOVQ	CX, ""..autotmp_5+168(SP)
0x0114 00276 (main.go:9)	MOVQ	AX, "".g+112(SP)
0x0119 00281 (main.go:9)	MOVQ	CX, "".g+120(SP)
0x011e 00286 (main.go:10)	MOVQ	"".g+112(SP), CX

再分析assertI2I2返回false的情况, 从之前的代码。能看到jump 325(0x0145)
看下itab的值

go.itab.*"".LOL,"".Game SRODATA dupok size=40
	0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0010 40 91 75 d1 00 00 00 00 00 00 00 00 00 00 00 00  @.u.............
	0x0020 00 00 00 00 00 00 00 00                          ........
	rel 0+8 t=1 type."".Game+0
	rel 8+8 t=1 type.*"".LOL+0
	rel 24+8 t=-32767 "".(*LOL).start+0
	rel 32+8 t=-32767 "".(*LOL).stop+0
	// jump 325
	0x0145 00325 (main.go:7)	CMPL	""..autotmp_10+44(SP), $-1584800941
	0x014d 00333 (main.go:8)	JEQ	340
	0x014f 00335 (main.go:8)	JMP	496
	// JEQ	340  如果匹配上了,走到这里
	0x0154 00340 (main.go:16)	MOVQ	""..autotmp_8+128(SP), DX //DX=*itab
	0x015c 00348 (main.go:16)	LEAQ	go.itab."".LOL,"".Game(SB), SI //SI=*itab
	0x0163 00355 (main.go:16)	CMPQ	DX, SI //在比较下*itab是否相同
	0x0166 00358 (main.go:16)	JEQ	362
	0x0168 00360 (main.go:16)	JMP	369
	0x016a 00362 (main.go:16)	MOVL	$1, AX
	0x016f 00367 (main.go:16)	JMP	373
	0x0171 00369 (main.go:16)	XORL	AX, AX
	0x0173 00371 (main.go:16)	JMP	373
	0x0175 00373 (main.go:16)	MOVB	AL, ""..autotmp_9+43(SP)
	0x0179 00377 (main.go:16)	TESTB	AL, AL
	0x017b 00379 (main.go:16)	JNE	383
	0x017d 00381 (main.go:16)	JMP	491
	0x017f 00383 (main.go:16)	PCDATA	$1, $-1
	0x017f 00383 (main.go:16)	NOP
	0x0180 00384 (main.go:16)	JMP	386
	0x0182 00386 (main.go:17)	MOVUPS	X15, ""..autotmp_6+144(SP)
	0x018b 00395 (main.go:17)	MOVQ	"".s+80(SP), AX
	0x0190 00400 (main.go:17)	MOVQ	"".s+88(SP), DX
	0x0195 00405 (main.go:17)	LEAQ	go.itab."".LOL,"".Game(SB), SI
	0x019c 00412 (main.go:17)	NOP
	0x01a0 00416 (main.go:17)	CMPQ	AX, SI
	0x01a3 00419 (main.go:17)	JEQ	426
	0x01a5 00421 (main.go:17)	JMP	677
	0x01aa 00426 (main.go:17)	MOVQ	(DX), SI
	0x01ad 00429 (main.go:17)	MOVQ	8(DX), DX
	0x01b1 00433 (main.go:17)	MOVQ	SI, ""..autotmp_6+144(SP)
	0x01b9 00441 (main.go:17)	MOVQ	DX, ""..autotmp_6+152(SP)
	0x01c1 00449 (main.go:17)	MOVQ	SI, "".l+96(SP)
	0x01c6 00454 (main.go:17)	MOVQ	DX, "".l+104(SP)
	0x01cb 00459 (main.go:18)	MOVQ	"".l+96(SP), AX
	0x01d0 00464 (main.go:18)	MOVQ	"".l+104(SP), BX
	0x01d5 00469 (main.go:18)	LEAQ	go.string."lol call start"(SB), CX
	0x01dc 00476 (main.go:18)	MOVL	$14, DI
	0x01e1 00481 (main.go:18)	PCDATA	$1, $0
	0x01e1 00481 (main.go:18)	CALL	"".LOL.start(SB)

CMPL ""..autotmp_10+44(SP), $-1584800941就是在匹配hash值,