mirror of https://github.com/golang/go.git
hash/maphash: add WriteComparable and Comparable
By default, runtime.memhash is used. When purego is used, reflect is used to generate the same []byte with the same value, and then hash the []byte. Fixes #54670 Change-Id: Ibd0538a7dfb3d831c5145970cac7c910692bca69
This commit is contained in:
parent
ffb3e57401
commit
02df413842
|
|
@ -0,0 +1,2 @@
|
|||
pkg hash/maphash, func Comparable[$0 comparable](Seed, $0) uint64 #54670
|
||||
pkg hash/maphash, func WriteComparable[$0 comparable](*Hash, $0) #54670
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
New function [Comparable] returns the hash of comparable value v.
|
||||
New function [WriteComparable] adds x to the data hashed by [Hash].
|
||||
|
|
@ -12,6 +12,11 @@
|
|||
// (See crypto/sha256 and crypto/sha512 for cryptographic use.)
|
||||
package maphash
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/byteorder"
|
||||
)
|
||||
|
||||
// A Seed is a random value that selects the specific hash function
|
||||
// computed by a [Hash]. If two Hashes use the same Seeds, they
|
||||
// will compute the same hash values for any given input.
|
||||
|
|
@ -275,3 +280,24 @@ func (h *Hash) Size() int { return 8 }
|
|||
|
||||
// BlockSize returns h's block size.
|
||||
func (h *Hash) BlockSize() int { return len(h.buf) }
|
||||
|
||||
// Comparable returns the hash of comparable value v with the given seed
|
||||
// such that Comparable(s, v1) == Comparable(s, v2) if v1 == v2.
|
||||
// If v contains a floating-point NaN, then the hash is non-deterministically random.
|
||||
func Comparable[T comparable](seed Seed, v T) uint64 {
|
||||
abi.Escape(v)
|
||||
t := abi.TypeOf(v)
|
||||
len := t.Size()
|
||||
if len == 0 {
|
||||
return seed.s
|
||||
}
|
||||
return comparableF(seed.s, v, t)
|
||||
}
|
||||
|
||||
// WriteComparable adds x to the data hashed by h.
|
||||
func WriteComparable[T comparable](h *Hash, x T) {
|
||||
v := Comparable(h.seed, x)
|
||||
var buf []byte
|
||||
byteorder.LePutUint64(buf[:], v)
|
||||
h.Write(buf[:])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ package maphash
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"internal/abi"
|
||||
"internal/byteorder"
|
||||
"math/bits"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func rthash(buf []byte, seed uint64) uint64 {
|
||||
|
|
@ -92,3 +94,64 @@ func mix(a, b uint64) uint64 {
|
|||
hi, lo := bits.Mul64(a, b)
|
||||
return hi ^ lo
|
||||
}
|
||||
|
||||
var byteSliceTyp = reflect.TypeFor[[]byte]()
|
||||
|
||||
var strSliceTyp = reflect.TypeFor[string]()
|
||||
|
||||
func comparableF[T comparable](seed uint64, v T, t *abi.Type) uint64 {
|
||||
vv := reflect.ValueOf(v)
|
||||
typ := vv.Type()
|
||||
if typ == byteSliceTyp {
|
||||
return wyhash(vv.Bytes(), seed, uint64(vv.Len()))
|
||||
}
|
||||
if typ == strSliceTyp {
|
||||
return rthashString(vv.String(), seed)
|
||||
}
|
||||
buf := make([]byte, 0, typ.Size())
|
||||
buf = appendT(buf, vv)
|
||||
return wyhash(buf, seed, uint64(len(buf)))
|
||||
}
|
||||
|
||||
func appendT(buf []byte, v reflect.Value) []byte {
|
||||
switch v.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return byteorder.LeAppendUint64(buf, uint64(v.Int()))
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
|
||||
return byteorder.LeAppendUint64(buf, v.Uint())
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := range v.Len() {
|
||||
buf = appendT(buf, v.Index(i))
|
||||
}
|
||||
return buf
|
||||
case reflect.String:
|
||||
return append(buf, v.String()...)
|
||||
case reflect.Struct:
|
||||
for i := range v.NumField() {
|
||||
buf = appendT(buf, v.Field(i))
|
||||
}
|
||||
return buf
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
c := v.Complex()
|
||||
buf = byteorder.LeAppendUint64(buf, uint64(real(c)))
|
||||
return byteorder.LeAppendUint64(buf, uint64(imag(c)))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return byteorder.LeAppendUint64(buf, uint64(v.Float()))
|
||||
case reflect.Bool:
|
||||
return byteorder.LeAppendUint16(buf, btoi(v.Bool()))
|
||||
case reflect.UnsafePointer, reflect.Pointer:
|
||||
return byteorder.LeAppendUint64(buf, uint64(v.Pointer()))
|
||||
case reflect.Interface:
|
||||
a := v.InterfaceData()
|
||||
buf = byteorder.LeAppendUint64(buf, uint64(a[0]))
|
||||
return byteorder.LeAppendUint64(buf, uint64(a[1]))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func btoi(b bool) uint16 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
package maphash
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/unsafeheader"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
|
@ -41,3 +43,23 @@ func rthashString(s string, state uint64) uint64 {
|
|||
func randUint64() uint64 {
|
||||
return runtime_rand()
|
||||
}
|
||||
|
||||
func comparableF[T comparable](seed uint64, v T, t *abi.Type) uint64 {
|
||||
k := t.Kind()
|
||||
len := t.Size()
|
||||
ptr := unsafe.Pointer(&v)
|
||||
switch k {
|
||||
case abi.Slice:
|
||||
len = uintptr(((*unsafeheader.Slice)(unsafe.Pointer(&v))).Len) * t.Elem().Size()
|
||||
ptr = ((*unsafeheader.Slice)(unsafe.Pointer(&v))).Data
|
||||
case abi.String:
|
||||
len = uintptr(((*unsafeheader.String)(unsafe.Pointer(&v))).Len)
|
||||
ptr = ((*unsafeheader.String)(unsafe.Pointer(&v))).Data
|
||||
}
|
||||
if unsafe.Sizeof(uintptr(0)) == 8 {
|
||||
return uint64(runtime_memhash(ptr, uintptr(seed), len))
|
||||
}
|
||||
lo := runtime_memhash(ptr, uintptr(seed), len)
|
||||
hi := runtime_memhash(ptr, uintptr(seed>>32), len)
|
||||
return uint64(hi)<<32 | uint64(lo)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,6 +210,53 @@ func TestSeedFromReset(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
var a, b int = 2, 2
|
||||
var pa *int = &a
|
||||
seed := MakeSeed()
|
||||
if Comparable(seed, a) != Comparable(seed, b) {
|
||||
t.Fatal("Comparable(seed, 2) != Comparable(seed, 2)")
|
||||
}
|
||||
old := Comparable(seed, pa)
|
||||
stackGrow(8192)
|
||||
new := Comparable(seed, pa)
|
||||
if old != new {
|
||||
t.Fatal("Comparable(seed, ptr) != Comparable(seed, ptr)")
|
||||
}
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func stackGrow(dep int) {
|
||||
if dep == 0 {
|
||||
return
|
||||
}
|
||||
var local [1024]byte
|
||||
_ = local
|
||||
stackGrow(dep - 1)
|
||||
}
|
||||
|
||||
func TestWriteComparable(t *testing.T) {
|
||||
var a, b int = 2, 2
|
||||
var pa *int = &a
|
||||
h1 := Hash{}
|
||||
h2 := Hash{}
|
||||
h1.seed = MakeSeed()
|
||||
h2.seed = h1.seed
|
||||
WriteComparable(&h1, a)
|
||||
WriteComparable(&h2, b)
|
||||
if h1.Sum64() != h1.Sum64() {
|
||||
t.Fatal("WriteComparable(h, 2) != WriteComparable(h, 2)")
|
||||
}
|
||||
WriteComparable(&h1, pa)
|
||||
old := h1.Sum64()
|
||||
stackGrow(8192)
|
||||
WriteComparable(&h2, pa)
|
||||
new := h2.Sum64()
|
||||
if old != new {
|
||||
t.Fatal("WriteComparable(seed, ptr) != WriteComparable(seed, ptr)")
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure a Hash implements the hash.Hash and hash.Hash64 interfaces.
|
||||
var _ hash.Hash = &Hash{}
|
||||
var _ hash.Hash64 = &Hash{}
|
||||
|
|
|
|||
Loading…
Reference in New Issue