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