go 汇编笔记

JLS 指令

CMPQ CX,$3
JLS  48  

JLS(通JBE一个意思)转移条件:CMPQ CX,$3 ;JLS 48 当CX的值小于或等于3的时候转跳到48。

编译器优化的例子

go标准库binary.BigEndian中的bigEndian.PutUint32

func PutUint32(b []byte, v uint32) {
	_ = b[3] // early bounds check to guarantee safety of writes below
	b[0] = byte(v >> 24)
	b[1] = byte(v >> 16)
	b[2] = byte(v >> 8)
	b[3] = byte(v)
}

这个例子 _ = b[3] 这个语句对于的汇编是

$ go tool compile -S main.go |grep main.go:10 
        0x000e 00014 (main.go:10)       PCDATA  $0, $0
        0x000e 00014 (main.go:10)       PCDATA  $1, $0
        0x000e 00014 (main.go:10)       MOVQ    "".b+40(SP), CX
        0x0013 00019 (main.go:10)       CMPQ    CX, $3
        0x0017 00023 (main.go:10)       JLS     48
        0x0030 00048 (main.go:10)       MOVL    $3, AX
        0x0035 00053 (main.go:10)       CALL    runtime.panicIndex(SB)
        0x003a 00058 (main.go:10)       XCHGL   AX, AX

意思很明显是提前判断一下是不是slice 下标越界。 但是这个 为什么不直接写成如下的样子,不是也会检查 panic,而不需要额外的 _ = b[3]

func PutUint32(b []byte, v uint32) {
	b[3] = byte(v)
	b[2] = byte(v >> 8)
	b[1] = byte(v >> 16)
	b[0] = byte(v >> 24)
}

其实这边就是涉及编译器优化的问题,我们看下原先 PutUint32的汇编去掉gc的代码

"".PutUint32 STEXT nosplit size=59 args=0x20 locals=0x18
        0x0000 00000 (main.go:9)        TEXT    "".PutUint32(SB), NOSPLIT|ABIInternal, $24-32
        0x0000 00000 (main.go:9)        SUBQ    $24, SP
        0x0004 00004 (main.go:9)        MOVQ    BP, 16(SP)
        0x0009 00009 (main.go:9)        LEAQ    16(SP), BP
        0x000e 00014 (main.go:10)       MOVQ    "".b+40(SP), CX //这边是回去slice len的值
        0x0013 00019 (main.go:10)       CMPQ    CX, $3
        0x0017 00023 (main.go:10)       JLS     48
        0x0019 00025 (main.go:11)       MOVL    "".v+56(SP), AX
        0x001d 00029 (main.go:11)       BSWAPL  AX
        0x001f 00031 (main.go:14)       MOVQ    "".b+32(SP), CX
        0x0024 00036 (main.go:14)       MOVL    AX, (CX)
        0x0026 00038 (main.go:15)       MOVQ    16(SP), BP
        0x002b 00043 (main.go:15)       ADDQ    $24, SP
        0x002f 00047 (main.go:15)       RET
        0x0030 00048 (main.go:10)       MOVL    $3, AX
        0x0035 00053 (main.go:10)       CALL    runtime.panicIndex(SB)
        0x003a 00058 (main.go:10)       XCHGL   AX, AX

从这段汇编来看,你会看到

	b[3] = byte(v)
	b[2] = byte(v >> 8)
	b[1] = byte(v >> 16)
    b[0] = byte(v >> 24)

这个已经直接被优化掉

MOVL    "".v+56(SP), AX
BSWAPL  AX          //指令作用是:32位寄存器内的字节次序变反。比如:(EAX)=9668 8368H,执行指令:BSWAP EAX ,则(EAX)=6883 6896H。

BSWAPL 指令做的工作就和上4个做的工作一样。

我们再看看乱序后的汇编代码

"".PutUint32 STEXT nosplit size=79 args=0x20 locals=0x18
        0x0000 00000 (main.go:9)        TEXT    "".PutUint32(SB), NOSPLIT|ABIInternal, $24-32
        0x0000 00000 (main.go:9)        SUBQ    $24, SP
        0x0004 00004 (main.go:9)        MOVQ    BP, 16(SP)
        0x0009 00009 (main.go:9)        LEAQ    16(SP), BP
        0x000e 00014 (main.go:10)       MOVQ    "".b+40(SP), CX
        0x0013 00019 (main.go:10)       CMPQ    CX, $3
        0x0017 00023 (main.go:10)       JLS     68
        0x0019 00025 (main.go:11)       MOVL    "".v+56(SP), AX
        0x001d 00029 (main.go:11)       MOVQ    "".b+32(SP), CX
        0x0022 00034 (main.go:11)       MOVB    AL, 3(CX)
        0x0025 00037 (main.go:12)       MOVL    AX, DX
        0x0027 00039 (main.go:12)       SHRL    $8, AX
        0x002a 00042 (main.go:12)       MOVB    AL, 2(CX)
        0x002d 00045 (main.go:13)       MOVL    DX, AX
        0x002f 00047 (main.go:13)       SHRL    $16, DX
        0x0032 00050 (main.go:13)       MOVB    DL, 1(CX)
        0x0035 00053 (main.go:14)       SHRL    $24, AX
        0x0038 00056 (main.go:14)       MOVB    AL, (CX)
        0x003a 00058 (main.go:15)       MOVQ    16(SP), BP
        0x003f 00063 (main.go:15)       ADDQ    $24, SP
        0x0043 00067 (main.go:15)       RET
        0x0044 00068 (main.go:10)       MOVL    $3, AX
        0x0049 00073 (main.go:10)       CALL    runtime.panicIndex(SB)
        0x004e 00078 (main.go:10)       XCHGL   AX, AX

明显看出来,乱序后没有编译器指令优化。

从这里我们就可以知道为什么要先写_ = b[3]这样语句判断下下标边界,而后续的四行代码被编译器优化后 只有对 v 参数做BSWAPL也就没有检查边界的地方。