go: hash/crc32: panic on arm64 with go1.21.0 when indexing slice

What version of Go are you using (go version)?

$ go version
go version go1.21.0 darwin/arm64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/edward/Library/Caches/go-build'
GOENV='/Users/edward/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/edward/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/edward/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.21.0/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.21.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.21.0'
GCCGO='gccgo'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/ld/l69fjx9x3gl6qfx056xt89hr0000gp/T/go-build4293017448=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

This code panics on go 1.21 on arm64 but runs fine on other platforms or go 1.20 arm64:

It doesn’t occur if I add a fmt.Println(index) or debug the application, before calling colors[index]

package main

import (
	"fmt"
	"hash/crc32"
)

func pickColor(userID string) string {
	colors := []string{
		"ffd179",
		"f388a8",
		"b786d2",
		"bed3f4",
		"88a0f3",
		"6fe8d9",
		"e86f85",
	}
	hash := crc32.ChecksumIEEE([]byte(userID))
	index := hash % uint32(len(colors))
	return colors[index]
}

func main() {
	fmt.Println(pickColor("jHjDiYOWTOS53K9t3XbOU7QXAwD3"))
}

What did you expect to see?

A string printed

What did you see instead?

panic: runtime error: index out of range [-4294967295]

goroutine 1 [running]:
main.pickColor({0x10494b15b?, 0x0?})
        /Users/edward/Desktop/go_bug.go:20 +0x118
main.main()
        /Users/edward/Desktop/go_bug.go:24 +0x28
exit status 2

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Reactions: 7
  • Comments: 15 (10 by maintainers)

Commits related to this issue

Most upvoted comments

The rule is only valid if all our rewrite rules preserve the upper-32-bits-are-zeroed property.

I’ve hacked up a change that fixes all the rules to preserve the semantics of the upper 32 bits. For example, when rewriting MULW with constant -1 argument, use NEGW and not NEG. It’s a pretty rote change but kind of large. Not something I would want to backport. I think we should revert CL 427454 on the 1.21 release branch and do my change for 1.22.

I worry other architectures have the same issue, as lots of those were started by copying the arm64 port. I will investigate.

Looks like the MOVWUreg is removed when its arg is a MSUBW, which is guaranteed to zero the upper 32 bits. But then later that arg gets changed to something that doesn’t zero the upper 32 bits, a SUB. So I think the logic that we don’t need the zero extension if its arg does the zero extension itself, is invalid if we also have the rule that rewrites of 32-bit-typed values can change the upper 32 bits. The rule is only valid if all our rewrite rules preserve the upper-32-bits-are-zeroed property. Unless we do the MOVWUreg removal in a separate pass after all other rewrites are done.

Simple reproducer:

package main

//go:noinline
func f() uint32 {
	return x - y
}
var x uint32 = 0
var y uint32 = 1
var z [2]int

func main() {
	_ = z[f() % 3]
}

Looks like there is a missing MOVWU in the % computation in 1.21.

Since you can reproduce the problem in assembly, it seems likely that this is a compiler or runtime change.

Can you build the Go repo from source and use git bisect to find the exact commit at which the regression began?