Description
Consider the following program:
package x
//go:nosplit
func x(v *int, b bool) unsafe.Pointer {
if b {
return unsafe.Pointer(v)
}
return unsafe.Pointer(&v)
}
Naively, one expects this to produce assembly like this:
TEXT .x(SB)
TBZ $0, R1, 1f
MOVD R0, 8(RSP)
MOVD type:*int, R0
CALL runtime.newobject
MOVD 8(RSP), R1
MOVD R1, (R0)
1:
RET
However, Go does something very naughty: it moves the call to the allocator before the branch.
TEXT x.x(SB)
MOVD.W R30, -48(RSP)
MOVD R29, -8(RSP)
SUB $8, RSP, R29
MOVB R1, 8(FP)
MOVD R0, (FP)
MOVD $type:*int(SB), R0
CALL runtime.newobject(SB)
MOVWU runtime.writeBarrier(SB), R1
CBZW 1f
MOVD R0, -8(SP)
MOVD R0, R1
MOVD $v(FP), R2
MOVD $type:*int(SB), R0
CALL runtime.wbMove(SB)
MOVD -8(SP), R0
1:
MOVD (FP), R1
MOVD R1, (R0)
MOVBU 8(FP), R1
TBZ 2f
MOVD (R0), R0
MOVD -8(RSP), R29
MOVD.P 48(RSP), R30
RET (R30)
2:
MOVD -8(RSP), R29
MOVD.P 48(RSP), R30
RET (R30)
This seems unfortunate, but seems very fixable: observe that the the stack slot's address being taken is the last time v is referenced in the function. In an SSA compiler, you would simply check that the place where v's address is taken does not dominate any uses of v. Not sure if that's easy for Go to do, though, since I'm not sure how it represents stack slots.
Alternatively, you could look for returns that are not dominated by taking the address; in that case, you have a path through the function that can avoid the allocation. Then, sink the allocation to blocks which dominate taking the address, but which do not dominate those allocation-free returns.
This can be worked around by doing something like v2 := v; &v2
or p := new(*int); *p = v
. The fact that a small perturbation causes an unnecessary allocation suggests there's other opportunities elsewhere to make early returns cheaper.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status