Skip to content

cmd/compile: support conditional escapes by splitting variables #74364

Open
@mcy

Description

@mcy

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

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions