aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Donovan <adonovan@google.com>2026-03-30 13:38:25 -0400
committerAlan Donovan <adonovan@google.com>2026-04-02 09:04:12 -0700
commitb19909b99275002dea7b54675a12a7b9c96f61e1 (patch)
treed1033f686f679b0a9f7cead81fba115e7cfec48e
parent7bbb5a8ec139cce0d126d00de16b167c2512ca1b (diff)
downloadgo-b19909b99275002dea7b54675a12a7b9c96f61e1.tar.xz
hash/maphash: revert purego implementation
This CL reverts the purego implementation added in CL 468795 and folds the runtime variant back into the main file. Purego means "no assembly"; it does not mean "no linkname". Henceforth this package will once again call runtime.memhash unconditionally. This was not a clean revert as there were a number of CLs since. The motivation for this change is to avoid the maphash -> crypto/rand dependency; see discussion on CL 657297. Change-Id: I54ce223c5b484710ce303cdec20049146df3f8bc Reviewed-on: https://go-review.googlesource.com/c/go/+/761260 Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
-rw-r--r--src/cmd/dist/test.go8
-rw-r--r--src/go/build/deps_test.go5
-rw-r--r--src/hash/maphash/maphash.go53
-rw-r--r--src/hash/maphash/maphash_purego.go200
-rw-r--r--src/hash/maphash/maphash_runtime.go64
-rw-r--r--src/hash/maphash/maphash_test.go3
6 files changed, 53 insertions, 280 deletions
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go
index 99488270d3..2c1cc27d58 100644
--- a/src/cmd/dist/test.go
+++ b/src/cmd/dist/test.go
@@ -701,14 +701,6 @@ func (t *tester) registerTests() {
tags: []string{"osusergo"},
pkg: "os/user",
})
- t.registerTest("hash/maphash purego implementation",
- &goTest{
- variant: "purego",
- timeout: 300 * time.Second,
- tags: []string{"purego"},
- pkg: "hash/maphash",
- env: []string{"GODEBUG=fips140=off"}, // FIPS 140-3 mode is incompatible with purego
- })
}
// Check that all crypto packages compile with the purego build tag.
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index b01136376e..7b3a8933c7 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -289,7 +289,7 @@ var depsRules = `
# hashes
io
< hash
- < hash/adler32, hash/crc32, hash/crc64, hash/fnv;
+ < hash/adler32, hash/crc32, hash/crc64, hash/fnv, hash/maphash;
# math/big
FMT, math/rand
@@ -626,9 +626,6 @@ var depsRules = `
crypto/tls
< net/smtp;
- crypto/rand
- < hash/maphash; # for purego implementation
-
# HTTP, King of Dependencies.
context
diff --git a/src/hash/maphash/maphash.go b/src/hash/maphash/maphash.go
index c6f3e62b5d..2629e33453 100644
--- a/src/hash/maphash/maphash.go
+++ b/src/hash/maphash/maphash.go
@@ -15,6 +15,8 @@ package maphash
import (
"hash"
"internal/abi"
+ "internal/runtime/maps"
+ "unsafe"
)
// A Seed is a random value that selects the specific hash function
@@ -299,7 +301,7 @@ func Comparable[T comparable](seed Seed, v T) uint64 {
// WriteComparable adds x to the data hashed by h.
func WriteComparable[T comparable](h *Hash, x T) {
abi.EscapeNonString(x)
- // writeComparable (not in purego mode) directly operates on h.state
+ // writeComparable directly operates on h.state
// without using h.buf. Mix in the buffer length so it won't
// commute with a buffered write, which either changes h.n or changes
// h.state.
@@ -308,3 +310,52 @@ func WriteComparable[T comparable](h *Hash, x T) {
}
writeComparable(h, x)
}
+
+//go:linkname runtime_rand runtime.rand
+func runtime_rand() uint64
+
+//go:linkname runtime_memhash runtime.memhash
+//go:noescape
+func runtime_memhash(p unsafe.Pointer, seed, s uintptr) uintptr
+
+func rthash(buf []byte, seed uint64) uint64 {
+ if len(buf) == 0 {
+ return seed
+ }
+ len := len(buf)
+ // The runtime hasher only works on uintptr. For 64-bit
+ // architectures, we use the hasher directly. Otherwise,
+ // we use two parallel hashers on the lower and upper 32 bits.
+ if maps.Use64BitHash {
+ return uint64(runtime_memhash(unsafe.Pointer(&buf[0]), uintptr(seed), uintptr(len)))
+ }
+ lo := runtime_memhash(unsafe.Pointer(&buf[0]), uintptr(uint32(seed)), uintptr(len))
+ hi := runtime_memhash(unsafe.Pointer(&buf[0]), uintptr(seed>>32), uintptr(len))
+ return uint64(hi)<<32 | uint64(lo)
+}
+
+func rthashString(s string, state uint64) uint64 {
+ buf := unsafe.Slice(unsafe.StringData(s), len(s))
+ return rthash(buf, state)
+}
+
+func randUint64() uint64 {
+ return runtime_rand()
+}
+
+func comparableHash[T comparable](v T, seed Seed) uint64 {
+ s := seed.s
+ var m map[T]struct{}
+ mTyp := abi.TypeOf(m)
+ hasher := (*abi.MapType)(unsafe.Pointer(mTyp)).Hasher
+ if maps.Use64BitHash {
+ return uint64(hasher(abi.NoEscape(unsafe.Pointer(&v)), uintptr(s)))
+ }
+ lo := hasher(abi.NoEscape(unsafe.Pointer(&v)), uintptr(uint32(s)))
+ hi := hasher(abi.NoEscape(unsafe.Pointer(&v)), uintptr(s>>32))
+ return uint64(hi)<<32 | uint64(lo)
+}
+
+func writeComparable[T comparable](h *Hash, v T) {
+ h.state.s = comparableHash(v, h.state)
+}
diff --git a/src/hash/maphash/maphash_purego.go b/src/hash/maphash/maphash_purego.go
deleted file mode 100644
index e286c5a5aa..0000000000
--- a/src/hash/maphash/maphash_purego.go
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build purego
-
-package maphash
-
-import (
- "crypto/rand"
- "errors"
- "internal/byteorder"
- "math"
- "math/bits"
- "reflect"
-)
-
-const purego = true
-
-var hashkey [4]uint64
-
-func init() {
- for i := range hashkey {
- hashkey[i] = randUint64()
- }
-}
-
-func rthash(buf []byte, seed uint64) uint64 {
- if len(buf) == 0 {
- return seed
- }
- return wyhash(buf, seed, uint64(len(buf)))
-}
-
-func rthashString(s string, state uint64) uint64 {
- return rthash([]byte(s), state)
-}
-
-func randUint64() uint64 {
- buf := make([]byte, 8)
- _, _ = rand.Read(buf)
- return byteorder.LEUint64(buf)
-}
-
-// This is a port of wyhash implementation in runtime/hash64.go,
-// without using unsafe for purego.
-
-const m5 = 0x1d8e4e27c47d124f
-
-func wyhash(key []byte, seed, len uint64) uint64 {
- p := key
- i := len
- var a, b uint64
- seed ^= hashkey[0]
-
- if i > 16 {
- if i > 48 {
- seed1 := seed
- seed2 := seed
- for ; i > 48; i -= 48 {
- seed = mix(r8(p)^hashkey[1], r8(p[8:])^seed)
- seed1 = mix(r8(p[16:])^hashkey[2], r8(p[24:])^seed1)
- seed2 = mix(r8(p[32:])^hashkey[3], r8(p[40:])^seed2)
- p = p[48:]
- }
- seed ^= seed1 ^ seed2
- }
- for ; i > 16; i -= 16 {
- seed = mix(r8(p)^hashkey[1], r8(p[8:])^seed)
- p = p[16:]
- }
- }
- switch {
- case i == 0:
- return seed
- case i < 4:
- a = r3(p, i)
- default:
- n := (i >> 3) << 2
- a = r4(p)<<32 | r4(p[n:])
- b = r4(p[i-4:])<<32 | r4(p[i-4-n:])
- }
- return mix(m5^len, mix(a^hashkey[1], b^seed))
-}
-
-func r3(p []byte, k uint64) uint64 {
- return (uint64(p[0]) << 16) | (uint64(p[k>>1]) << 8) | uint64(p[k-1])
-}
-
-func r4(p []byte) uint64 {
- return uint64(byteorder.LEUint32(p))
-}
-
-func r8(p []byte) uint64 {
- return byteorder.LEUint64(p)
-}
-
-func mix(a, b uint64) uint64 {
- hi, lo := bits.Mul64(a, b)
- return hi ^ lo
-}
-
-func comparableHash[T comparable](v T, seed Seed) uint64 {
- var h Hash
- h.SetSeed(seed)
- writeComparable(&h, v)
- return h.Sum64()
-}
-
-func writeComparable[T comparable](h *Hash, v T) {
- vv := reflect.ValueOf(v)
- appendT(h, vv)
-}
-
-// appendT hash a value.
-func appendT(h *Hash, v reflect.Value) {
- h.WriteString(v.Type().String())
- switch v.Kind() {
- case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
- var buf [8]byte
- byteorder.LEPutUint64(buf[:], uint64(v.Int()))
- h.Write(buf[:])
- return
- case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
- var buf [8]byte
- byteorder.LEPutUint64(buf[:], v.Uint())
- h.Write(buf[:])
- return
- case reflect.Array:
- var buf [8]byte
- for i := range uint64(v.Len()) {
- byteorder.LEPutUint64(buf[:], i)
- // do not want to hash to the same value,
- // [2]string{"foo", ""} and [2]string{"", "foo"}.
- h.Write(buf[:])
- appendT(h, v.Index(int(i)))
- }
- return
- case reflect.String:
- h.WriteString(v.String())
- return
- case reflect.Struct:
- var buf [8]byte
- for i := range v.NumField() {
- f := v.Field(i)
- byteorder.LEPutUint64(buf[:], uint64(i))
- // do not want to hash to the same value,
- // struct{a,b string}{"foo",""} and
- // struct{a,b string}{"","foo"}.
- h.Write(buf[:])
- appendT(h, f)
- }
- return
- case reflect.Complex64, reflect.Complex128:
- c := v.Complex()
- h.float64(real(c))
- h.float64(imag(c))
- return
- case reflect.Float32, reflect.Float64:
- h.float64(v.Float())
- return
- case reflect.Bool:
- h.WriteByte(btoi(v.Bool()))
- return
- case reflect.UnsafePointer, reflect.Pointer, reflect.Chan:
- var buf [8]byte
- // because pointing to the abi.Escape call in comparableReady,
- // So this is ok to hash pointer,
- // this way because we know their target won't be moved.
- byteorder.LEPutUint64(buf[:], uint64(v.Pointer()))
- h.Write(buf[:])
- return
- case reflect.Interface:
- appendT(h, v.Elem())
- return
- }
- panic(errors.New("maphash: hash of unhashable type " + v.Type().String()))
-}
-
-func (h *Hash) float64(f float64) {
- if f == 0 {
- h.WriteByte(0)
- return
- }
- var buf [8]byte
- if f != f {
- byteorder.LEPutUint64(buf[:], randUint64())
- h.Write(buf[:])
- return
- }
- byteorder.LEPutUint64(buf[:], math.Float64bits(f))
- h.Write(buf[:])
-}
-
-func btoi(b bool) byte {
- if b {
- return 1
- }
- return 0
-}
diff --git a/src/hash/maphash/maphash_runtime.go b/src/hash/maphash/maphash_runtime.go
deleted file mode 100644
index 5ae23a0218..0000000000
--- a/src/hash/maphash/maphash_runtime.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !purego
-
-package maphash
-
-import (
- "internal/abi"
- "internal/runtime/maps"
- "unsafe"
-)
-
-const purego = false
-
-//go:linkname runtime_rand runtime.rand
-func runtime_rand() uint64
-
-//go:linkname runtime_memhash runtime.memhash
-//go:noescape
-func runtime_memhash(p unsafe.Pointer, seed, s uintptr) uintptr
-
-func rthash(buf []byte, seed uint64) uint64 {
- if len(buf) == 0 {
- return seed
- }
- len := len(buf)
- // The runtime hasher only works on uintptr. For 64-bit
- // architectures, we use the hasher directly. Otherwise,
- // we use two parallel hashers on the lower and upper 32 bits.
- if maps.Use64BitHash {
- return uint64(runtime_memhash(unsafe.Pointer(&buf[0]), uintptr(seed), uintptr(len)))
- }
- lo := runtime_memhash(unsafe.Pointer(&buf[0]), uintptr(uint32(seed)), uintptr(len))
- hi := runtime_memhash(unsafe.Pointer(&buf[0]), uintptr(seed>>32), uintptr(len))
- return uint64(hi)<<32 | uint64(lo)
-}
-
-func rthashString(s string, state uint64) uint64 {
- buf := unsafe.Slice(unsafe.StringData(s), len(s))
- return rthash(buf, state)
-}
-
-func randUint64() uint64 {
- return runtime_rand()
-}
-
-func comparableHash[T comparable](v T, seed Seed) uint64 {
- s := seed.s
- var m map[T]struct{}
- mTyp := abi.TypeOf(m)
- hasher := (*abi.MapType)(unsafe.Pointer(mTyp)).Hasher
- if maps.Use64BitHash {
- return uint64(hasher(abi.NoEscape(unsafe.Pointer(&v)), uintptr(s)))
- }
- lo := hasher(abi.NoEscape(unsafe.Pointer(&v)), uintptr(uint32(s)))
- hi := hasher(abi.NoEscape(unsafe.Pointer(&v)), uintptr(s>>32))
- return uint64(hi)<<32 | uint64(lo)
-}
-
-func writeComparable[T comparable](h *Hash, v T) {
- h.state.s = comparableHash(v, h.state)
-}
diff --git a/src/hash/maphash/maphash_test.go b/src/hash/maphash/maphash_test.go
index c9ef1777ec..35776f5a59 100644
--- a/src/hash/maphash/maphash_test.go
+++ b/src/hash/maphash/maphash_test.go
@@ -428,9 +428,6 @@ func TestWriteComparableNoncommute(t *testing.T) {
}
func TestComparableAllocations(t *testing.T) {
- if purego {
- t.Skip("skip allocation test in purego mode - reflect-based implementation allocates more")
- }
if asan.Enabled {
t.Skip("skip allocation test under -asan")
}