Description
Most testing done using go version go1.10.1 linux/amd64
I have tried a bunch of releases, from 1.7, to 1.10.3
Caveat: I may just be confused about something, but I have spent quite some time trying to come up with reasons why the generated code makes sense and the mailing list didn't provide an answer either.
The pointer arithmetic pattern described in the unsafe
documentation seems to cause the compiler to emit assembly to calculate and dereference the same pointer twice. The simplest way to reproduce this is:
func getElement1(a unsafe.Pointer) (r uint64) {
return *(*uint64)(unsafe.Pointer(uintptr(a) + 8))
}
Looking at the (shortened) Go assembly we see this:
MOVQ "".a(SP), AX
LEAQ 8(AX), CX
TESTB AX, (CX)
MOVQ 8(AX), AX
MOVQ AX, "".r+8(SP)
I would have expected something like this function (which I have tested and it seems to have the same behavior):
// func getElement1Asm(a unsafe.Pointer) (r uint64)
TEXT ·getElement1Asm(SB),NOSPLIT,$0
MOVQ a+8(SP), R8 // Why 8 instead of 0 like above?
MOVQ 8(R8), R8
MOVQ R8, r+16(SP)
RET
I have tried to come up with explanations for what the LEAQ, TESTB sequence is good for, but failed. It seems like the compiler is inserting the equivalent of _ = *(*uint64)(unsafe.Pointer(uintptr(a) + 8))
before the return statement.
I have tested countless variants of similar and not so similar functions containing pointer arithmetic and the result is always that both address calculation and dereference are duplicated, if an address is changed by a non-zero amount (*(*uint64)(unsafe.Pointer(uintptr(a) + 0))
doesn't have this issue).
I realize that this technically doesn't violate the spec or documented behavior, but the situation seems unfortunate (yes, I benchmarked the impact on less contrived examples).
ssa.html.log (renamed to make github happy)