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
也就没有检查边界的地方。