diff options
| author | Alan Donovan <adonovan@google.com> | 2026-03-30 13:38:25 -0400 |
|---|---|---|
| committer | Alan Donovan <adonovan@google.com> | 2026-04-02 09:04:12 -0700 |
| commit | b19909b99275002dea7b54675a12a7b9c96f61e1 (patch) | |
| tree | d1033f686f679b0a9f7cead81fba115e7cfec48e | |
| parent | 7bbb5a8ec139cce0d126d00de16b167c2512ca1b (diff) | |
| download | go-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.go | 8 | ||||
| -rw-r--r-- | src/go/build/deps_test.go | 5 | ||||
| -rw-r--r-- | src/hash/maphash/maphash.go | 53 | ||||
| -rw-r--r-- | src/hash/maphash/maphash_purego.go | 200 | ||||
| -rw-r--r-- | src/hash/maphash/maphash_runtime.go | 64 | ||||
| -rw-r--r-- | src/hash/maphash/maphash_test.go | 3 |
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") } |
