Skip to content

cmd/compile: regalloc dependent on variable definition order #80201

Description

@JunyangShao

Go version

go version go1.27-devel_06bb174f89 Mon Jun 29 10:05:16 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='/usr/local/google/home/shaojunyang/go/bin'
GOCACHE='/usr/local/google/home/shaojunyang/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/usr/local/google/home/shaojunyang/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build564480818=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/usr/local/google/home/shaojunyang/work/go/src/go.mod'
GOMODCACHE='/usr/local/google/home/shaojunyang/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPACKAGESDRIVER=''
GOPATH='/usr/local/google/home/shaojunyang/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/google/home/shaojunyang/work/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/usr/local/google/home/shaojunyang/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/google/home/shaojunyang/work/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.27-devel_06bb174f89 Mon Jun 29 10:05:16 2026 -0700'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I was playing around with my SROA pass to find regressions introduced by its code transformation, and found a noisy behavior of regalloc.

I constructed 2 benchmark functions, both of which consists of 64 variables and they are made circularly dependent inside a loop. See https://go.dev/play/p/iVH206DXP9L?v=gotip. (Playground will timeout on this, please copy them to local).

randomizedInit initializes the variables randomly, while kindoforderedInit initialized them like an arithmetic sequence. All variables are assigned to the same initial value. And the codes followed (the loop that introduced a loop-carried circular dependency) are the same for both benchmark functions.

These 2 functions should be semantically isomorphic, the only difference is that the variables' SSA values are listed in a different order. So I was expecting them to be roughly the same performance, but the result is pretty different:

Running benchmarks...
BenchmarkKindofordered:     5754             19112 ns/op
BenchmarkRandomized:        4636             26018 ns/op

It might be that a stack storming is happening for the randomizedInit benchmark: when merging edges, we have to move values that are in spilled around. When reaglloc tries to allocate a scratch register, it needs to spill more value. And somehow if the order of these chained spills are not aligned nicely, we end up moving values back and forth, i.e. doing a unnecessary stack permutation.

What did you see happen?

Running benchmarks...
BenchmarkKindofordered:     5754             19112 ns/op
BenchmarkRandomized:        4636             26018 ns/op

What did you expect to see?

The benchmarks perform the same.

Metadata

Metadata

Labels

compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions