Skip to content

cmd/compile: generic function reference in loop is not LICM'd out #78808

Description

@mcy

Consider the following program:

package p

import _ "unsafe"

func f[T any]() {}

func g[T any](n int) {
    for range n {
        escape(f[T])
    }
}

//go:linkname escape
func escape(func())

var x = g[int]

g passes f[T] to escape each loop iteration. Because g is itself generic, it cannot use a pre-baked thunk that sets the dictionary for the call to f, so it must allocate a closure. This is all well and good and expected.

However, let's look at the assembly.

TEXT    g[go.shape.int](SB)
        PUSHQ   BP
        MOVQ    SP, BP
        SUBQ    $32, SP
        MOVQ    AX, 48(SP)
        JMP    latch
backedge:
        MOVQ    CX, 8(AX)
        CALL    .h(SB)
        MOVQ    16(SP), BX
        DECQ    BX
        MOVQ    48(SP), AX
latch:
        TESTQ   BX, BX
        JLE     exit
        MOVQ    BX 16(SP)
        MOVQ    (AX), CX
        MOVQ    CX, 24(SP)
        LEAQ    type:noalg.struct { F uintptr; X0 *[0]uintptr }(SB), AX
        CALL    runtime.newobject(SB)
        LEAQ    .g[go.shape.int].func1(SB), CX
        MOVQ    CX, (AX)
        CMPL    runtime.writeBarrier(SB), $0 // NB: This write barrier seems redundant...?
        JNE     wb
        MOVQ    24(SP), CX
        JMP     backedge
wb:
        CALL    runtime.gcWriteBarrier1(SB)
        MOVQ    24(SP), CX
        MOVQ    CX, (R11)
        JMP     backedge
exit:
        ADDQ    $32, SP
        POPQ    BP
        RET

Notice that every trip through this loop, we allocate a brand new closure, despite the fact that this closure does not "capture" anything, except for the (immutable!) type dictionary.

There is a broader issue that closures which capture "immutable state" (i.e., no capture has a use after closure creation, and the closure does not write to any capture, both of which are trivial to check in SSA) may still be constructed more than once. This general version is much harder to do and probably not quite as valuable.

However, LICM is not necessary to deal with this specific case. We know statically that g[T] requires a closure for f[T], so the type dictionary for g[T] should simply contain an entry for f[T]'s funcval thunk. This can be extended to include anonymous closures that capture the type dictionary (see #78370, which is tangentially related).

Metadata

Metadata

Assignees

No one assigned

    Labels

    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

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions