Repository navigation
cmd/compile: closures created while inlining are not analyzed for further inlining #81380
Description
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
RETThis turns out to be maybe 40% of the slowdown in #81315.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsIn Progress
Activity
jakebailey commentedon Sep 7, 2026
Related:
gabyhelp commentedon Sep 7, 2026
Related Issues
Related Code Changes
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
gopherbot commentedon Sep 7, 2026
Change https://go.dev/cl/828569 mentions this issue:
encoding/json/v2: avoid inlining struct arshaler constructiongopherbot commentedon Sep 9, 2026
Change https://go.dev/cl/806341 mentions this issue:
cmd/compile/inline: inline into closures materialized while inliningJunyangShao commentedon Sep 11, 2026
@golang/compiler