Repository navigation
cmd/compile: inlined generic interface dispatch miscompiles int64 compare with -race on amd64 #81463
Description
Go version
go version go1.27.1 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='/go/bin'
GOCACHE='/root/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/root/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build755864626=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPACKAGESDRIVER=''
GOPATH='/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/root/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.27.1'
GOWORK=''
PKG_CONFIG='pkg-config'What did you do?
Run this standalone main.go with go run -race main.go on linux/amd64:
package main
type leftInt interface {
uint8 | int32
}
type rightInt interface {
uint8 | int64
}
func compare[L leftInt, R rightInt](left L, right R) int {
if int64(left) < int64(right) {
return 1
}
return -1
}
func main() {
if dispatch(uint8(0), int64(-1)) == 1 {
panic("bad")
}
}
func dispatch(l, r any) int {
if left, ok := l.(int32); ok {
right := r.(int64)
return compare(left, right)
}
left := l.(uint8)
switch right := r.(type) {
case int64:
return compare(left, right)
case uint8:
return compare(left, right)
default:
return 0
}
}What did you see happen?
With Go 1.27.1 linux/amd64 and -race, the program panics:
panic: bad
goroutine 1 [running]:
main.main()
/workspace/main.go:20 +0x5e
exit status 2
The result 1 means the compiled code evaluated
uint8(0) < int64(-1) as true.
What did you expect to see?
The program should exit successfully. uint8(0) is greater than int64(-1),
so dispatch should not return 1.
Codex notes
Only the uint8,int64 case is executed at runtime. The int32,int64 branch
and uint8,uint8 case are still required to make cmd/compile produce the bad
inlined generic interface-dispatch shape.
The reduced value pair is intentionally small. If the right int64(-1) value
is spilled and reloaded as uint8, only the low byte survives, so the reloaded
value is 255. The compiled code then compares uint8(0) against 255,
making < incorrectly true.
Other reduction notes:
required: Go 1.27 linux/amd64, -race, inlining
required: generic helper returns int from the comparison
required: interface assertion/dispatch branch shape around the executed case
not specific: extra left type can be int32, int8, int, or uint16
stopped reproducing: removing the extra branch, uint8 case, default case, or
right temporary in the unused branch
stopped reproducing: returning bool from compare, or panicking inside compare
The behavior is specific to Go 1.27 linux/amd64 with -race and inlining:
Go 1.26.8 linux/amd64 + -race: pass
Go 1.27.0 linux/amd64 + -race: fail
Go 1.27.1 linux/amd64 + -race: fail
Go 1.27.1 linux/amd64 without -race: pass
Go 1.27.1 linux/amd64 + -race -gcflags=all=-l: pass
Suspected problematic change
The likely introducing change is commit
80123ef4bf5397f6f194b0a54e8c65dadb18aa1b, CL 777820:
cmd/compile: preserve pointerness during splitload
That CL changed the 386 and AMD64 splitload rules so compare-with-memory
rewrites preserve the other compare operand's type:
-(CMP(Q|L|W|B)load {sym} [off] ptr x mem) =>
(CMP(Q|L|W|B) (MOV(Q|L|W|B)load {sym} [off] ptr mem) x)
+(CMP(Q|L|W|B)load {sym} [off] ptr x mem) =>
(CMP(Q|L|W|B) (MOV(Q|L|W|B)load <x.Type> {sym} [off] ptr mem) x)For pointer comparisons that makes sense: a pointer-typed load must remain
visible to stack maps if it is spilled. In this repro, however, the CMPQload
right operand is an int64 value and x.Type is the narrow unsigned left
operand type.
The unpatched Go 1.27.1 SSA for the failing uint8,int64 branch contains:
v130 = MOVQload <uint8> v92 v107 : CX
v156 = StoreReg <uint8> v130 : .autotmp_36[uint8]
CALLstatic {AuxCall{runtime.racefuncexit}}
v113 = LoadReg <uint8> v156 : CX
v125 = CMPQ <flags> v113 v110
So the machine load is 64-bit, but the SSA type is uint8. The race detector
call forces the value through a spill slot, and regalloc/codegen spills it using
the narrow SSA type.
The resulting assembly difference is just:
- MOVQ CX, 0x18(SP)
+ MOVB CL, 0xe(SP)
CALL runtime.racefuncexit(SB)
- MOVQ 0x18(SP), CX
+ MOVZX 0xe(SP), CXFor int64(-1), the low byte is 255. The Go 1.27.1 code therefore compares
the left uint8(0) against 255, not against the original negative int64,
and returns 1.
A directed check patched only the generated Go 1.27.1 AMD64
CMPQload/CMPQloadidx splitload rewrites back from x.Type to UInt64,
rebuilt cmd/compile, and reran this repro:
unpatched Go 1.27.1 go run -race main.go: panic: bad
patched Go 1.27.1 go run -race main.go: pass
That points to AMD64 splitload type selection plus spill width, not a runtime
race detector semantic issue. The race detector call appears to expose the bad
SSA type by forcing a spill.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIn Progress
Activity
gabyhelp commentedon Sep 10, 2026
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
randall77 commentedon Sep 11, 2026
I can confirm the breakage. Still broken at tip.
@josharian
JunyangShao commentedon Sep 11, 2026
@golang/compiler
josharian commentedon Sep 11, 2026
Thanks. Will look soon.
gopherbot commentedon Sep 12, 2026
Change https://go.dev/cl/831464 mentions this issue:
cmd/compile: preserve the width of split comparison loadsbdhill-arista commentedon Sep 21, 2026
Any updates on this?
Can we get this fix into the next minor release?
josharian commentedon Sep 22, 2026
Sorry, we got caught up in a question of the correct flavor of the fix. Will continue that conversation...
bdhill-arista commentedon Sep 25, 2026
@josharian saw your CL is still pending
We're wondering if this will make it into Go 1.27.2, and if it can be added to the Go 1.27.2 milestone
bdhill-arista commentedon Sep 30, 2026
@josharian @randall77 following up on this.
Is something you'd consider backporting to Go 1.27.2?
bdhill-arista commentedon Sep 30, 2026
Or does this seem like not that bad of a bug (wasn't able to repro without race detector enabled)