go: cmd/compile: unsafe.Pointer arithmetic causes insertion of redundant nil checks

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)

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 23 (21 by maintainers)

Commits related to this issue

Most upvoted comments

runtime.memhash runs into the same problem. I added a rule to replace Add(32|64) with AddPtr for ops with the unsafe.Pointer type and the code size reduced by about 10%. I don’t think this is a great solution though due to the reasons listed above.

Merging constant offsets into LoweredNilCheck (as @randall77 suggested) would solve this problem for most uses of pointer arithmetic without changing any observable behavior. I wonder if we should just do that, at least for now? I’m happy to send a CL.