Go 对接口运行类型断言的编译器输出进行解码
我最近在使用Go 对接口运行类型断言的编译器输出进行解码,go,Go,我最近在使用Atomic.Value的Load()方法时遇到了空接口。我尝试了一点空接口类型断言- 这引起了我的兴趣,我决定在幕后看一看编译器采取了哪些操作,以便在试图读取nilinterface{}(例如,当调用Load.(type)存储尚未调用时)上的具体值时,代码不会惊慌失措 我可以看到,在不安全的版本中,编译器有一条导致死机的汇编指令:call runtime.panicdottype(SB) 安全版本中显然不存在紧急指令。有人能更详细地解释一下,当我们用ok捕捉返回值时,编译器在做什么
Atomic.Value
的Load()方法时遇到了空接口。我尝试了一点空接口类型断言-
这引起了我的兴趣,我决定在幕后看一看编译器采取了哪些操作,以便在试图读取nil
interface{}(例如,当调用Load.(type)
存储尚未调用时)上的具体值时,代码不会惊慌失措
我可以看到,在不安全的版本中,编译器有一条导致死机的汇编指令:call runtime.panicdottype(SB)
安全版本中显然不存在紧急指令。有人能更详细地解释一下,当我们用ok捕捉返回值时,编译器在做什么(也许可以给我指一下godbolt链接中相应的汇编指令)
以下是不安全版本[1]和安全版本[2]的godbolt编译器链接
[1]
[2] 快速总结:它们做的事情完全相同,只是ok==false的情况不同
这两个位有以下共同点:
pcdata $0, $0
pcdata $1, $0
call "".returnEmptyInterface(SB)
pcdata $0, $1
movq 8(SP), AX
movq (SP), CX
pcdata $0, $2
leaq type.bool(SB), DX
cmpq CX, DX
jne main_pc156 <==== jump
pcdata $0, $3
movblzx (AX), AX
这是无法逃避的,一旦我们跳转到主pc156,我们就会惊慌失措
另一方面,双值类型断言的代码是:
main_pc186:
pcdata $0, $3
xorl AX, AX
jmp main_pc62
这与前一种情况大不相同,将我们带回到代码的第一位末尾,继续执行。空接口类型(在运行时包中称为eface
)有两个指针,第一个指向底层类型(例如bool类型、int类型、YourStruct类型,…),第二个是指向数据的指针(IIRC在某些情况下是指数据本身)
不安全版本:
call "".returnEmptyInterface(SB) Call the function
pcdata $0, $1 pcdata is not a real instruction, ignore
movq 8(SP), AX AX <- pointer to data
movq (SP), CX CX <- pointer to type
pcdata $0, $2
leaq type.bool(SB), DX DX <- pointer to bool type
cmpq CX, DX Compare CX and DX
jne main_pc156 If they are not equal jump to main_pc156
pcdata $0, $0
pcdata $1, $0
call "".returnEmptyInterface(SB) Call the function
pcdata $0, $1
movq 8(SP), AX AX <- pointer to data
movq (SP), CX CX <- pointer to type
main_pc47:
pcdata $0, $2
leaq type.bool(SB), DX DX <- pointer to bool type
cmpq CX, DX Compare CX and DX
jne main_pc186 If not equal jump main_pc186
pcdata $0, $3
movblzx (AX), AX AX <- dereference AX
main_pc62:
安全版本:
call "".returnEmptyInterface(SB) Call the function
pcdata $0, $1 pcdata is not a real instruction, ignore
movq 8(SP), AX AX <- pointer to data
movq (SP), CX CX <- pointer to type
pcdata $0, $2
leaq type.bool(SB), DX DX <- pointer to bool type
cmpq CX, DX Compare CX and DX
jne main_pc156 If they are not equal jump to main_pc156
pcdata $0, $0
pcdata $1, $0
call "".returnEmptyInterface(SB) Call the function
pcdata $0, $1
movq 8(SP), AX AX <- pointer to data
movq (SP), CX CX <- pointer to type
main_pc47:
pcdata $0, $2
leaq type.bool(SB), DX DX <- pointer to bool type
cmpq CX, DX Compare CX and DX
jne main_pc186 If not equal jump main_pc186
pcdata $0, $3
movblzx (AX), AX AX <- dereference AX
main_pc62:
所以编译器决定在打印时再次比较它们。上次我检查时,接口中的数据总是通过指针间接指向的,即使数据可以放在同一个插槽中。有些地方有评论说“我们计划改变这一点,尽量不要依赖它”或者类似的东西。因为这样的改变会弄乱ABI,所以在Go 2.x之前它可能不会发生。@torek我检查过,Go有了这个功能,它就被删除了。检查旧版本,就像它充满了IsDirectFace
checks。啊哈,所以我把方向倒过来了:它已经优化过了,并且已经去优化了!:-)(我现在想知道为什么。)
pcdata $0, $3
xorl AX, AX AX <- 0
jmp main_pc62 Jump back at the end of previous block
cmpq CX, DX Compare CX and DX
seteq AL AL <- 1 if CX equal to DX otherwise 0