Skip to content

cmd/compile: closures created while inlining are not analyzed for further inlining #81380

Open

Description

@jakebailey

Go version

go version go1.28-devel_38d1265e1 Sat Sep 5 15:11:17 2026 -0700 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN='/home/jake/go/bin'
GOCACHE='/home/jake/.cache/go-build'
GOCACHEPROG='gocachez'
GODEBUG=''
GOENV='/home/jake/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2827195013=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/jake/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPACKAGESDRIVER=''
GOPATH='/home/jake/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/jake/sdk/gotip'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/home/jake/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/jake/sdk/gotip/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.28-devel_38d1265e1 Sat Sep 5 15:11:17 2026 -0700'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Compile this code:

package p

func twice(x int) int {
    return x * 2
}

func makeFunc() func(int) int {
    return func(x int) int {
        return twice(x)
    }
}

//go:noinline
func use() func(int) int {
    return makeFunc()
}

Compile with:

go tool compile -m=2 -S repro.go >repro.s 2>&1

What did you see happen?

The diagnostics claim both relevant calls are inlined:

can inline makeFunc.func1 with cost 8
can inline makeFunc with cost 17
inlining call to twice
inlining call to makeFunc

However, inlining makeFunc into use creates a second closure body. use refers to that cloned body:

TEXT    p.use(SB), NOSPLIT|NOFRAME
LEAQ    p.makeFunc.func1#...·f(SB), AX
RET

The original closure is optimized as expected:

TEXT    p.makeFunc.func1(SB), NOSPLIT|NOFRAME
ADDQ    AX, AX
RET

But the closure cloned during inlining is not reanalyzed, so it retains the call:

TEXT    p.makeFunc.func1#...(SB), ...
CALL    p.twice(SB)
RET

Adding //go:noinline or moving code around into a struct + methods works around the problem.

What did you expect to see?

I expected the closure created by inlining makeFunc into use to receive the same inlining analysis as the original closure. In particular, the call to twice should be inlined, so both closure bodies should compile to the equivalent of:

ADDQ AX, AX
RET

This turns out to be maybe 40% of the slowdown in #81315.

Activity

  1. gopherbot commented on Sep 7, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/828569 mentions this issue: encoding/json/v2: avoid inlining struct arshaler construction

  2. gopherbot commented on Sep 9, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/806341 mentions this issue: cmd/compile/inline: inline into closures materialized while inlining

  3. JunyangShao commented on Sep 11, 2026

    @JunyangShao
    Contributor

    @golang/compiler

  4. added
    NeedsFixThe path to resolution is known, but the work has not been done.
    on Sep 11, 2026
  5. added
    FixPendingIssues that have a fix which has not yet been reviewed or submitted.
    on Sep 15, 2026
  6. added this to the Go1.28 milestone on Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

FixPendingIssues that have a fix which has not yet been reviewed or submitted.NeedsFixThe path to resolution is known, but the work has not been done.compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions