mirror of
https://github.com/golang/go
synced 2024-11-19 23:24:45 -07:00
91059de095
Improve the aeshash implementation to make it harder to engineer collisions. 1) Scramble the seed before xoring with the input string. This makes it harder to cancel known portions of the seed (like the size) because it mixes the per-table seed into those other parts. 2) Use table-dependent seeds for all stripes when hashing >16 byte strings. For small strings this change uses 4 aesenc ops instead of 3, so it is somewhat slower. The first two can run in parallel, though, so it isn't 33% slower. benchmark old ns/op new ns/op delta BenchmarkHash64-12 10.2 11.2 +9.80% BenchmarkHash16-12 5.71 6.13 +7.36% BenchmarkHash5-12 6.64 7.01 +5.57% BenchmarkHashBytesSpeed-12 30.3 31.9 +5.28% BenchmarkHash65536-12 2785 2882 +3.48% BenchmarkHash1024-12 53.6 55.4 +3.36% BenchmarkHashStringArraySpeed-12 54.9 56.5 +2.91% BenchmarkHashStringSpeed-12 18.7 19.2 +2.67% BenchmarkHashInt32Speed-12 14.8 15.1 +2.03% BenchmarkHashInt64Speed-12 14.5 14.5 +0.00% Change-Id: I59ea124b5cb92b1c7e8584008257347f9049996c Reviewed-on: https://go-review.googlesource.com/14124 Reviewed-by: jcd . <jcd@golang.org> Run-TryBot: Keith Randall <khr@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
343 lines
8.4 KiB
Go
343 lines
8.4 KiB
Go
// Copyright 2014 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.
|
|
|
|
package runtime
|
|
|
|
import "unsafe"
|
|
|
|
const (
|
|
c0 = uintptr((8-ptrSize)/4*2860486313 + (ptrSize-4)/4*33054211828000289)
|
|
c1 = uintptr((8-ptrSize)/4*3267000013 + (ptrSize-4)/4*23344194077549503)
|
|
)
|
|
|
|
// type algorithms - known to compiler
|
|
const (
|
|
alg_MEM = iota
|
|
alg_MEM0
|
|
alg_MEM8
|
|
alg_MEM16
|
|
alg_MEM32
|
|
alg_MEM64
|
|
alg_MEM128
|
|
alg_NOEQ
|
|
alg_NOEQ0
|
|
alg_NOEQ8
|
|
alg_NOEQ16
|
|
alg_NOEQ32
|
|
alg_NOEQ64
|
|
alg_NOEQ128
|
|
alg_STRING
|
|
alg_INTER
|
|
alg_NILINTER
|
|
alg_SLICE
|
|
alg_FLOAT32
|
|
alg_FLOAT64
|
|
alg_CPLX64
|
|
alg_CPLX128
|
|
alg_max
|
|
)
|
|
|
|
// typeAlg is also copied/used in reflect/type.go.
|
|
// keep them in sync.
|
|
type typeAlg struct {
|
|
// function for hashing objects of this type
|
|
// (ptr to object, seed) -> hash
|
|
hash func(unsafe.Pointer, uintptr) uintptr
|
|
// function for comparing objects of this type
|
|
// (ptr to object A, ptr to object B) -> ==?
|
|
equal func(unsafe.Pointer, unsafe.Pointer) bool
|
|
}
|
|
|
|
func memhash0(p unsafe.Pointer, h uintptr) uintptr {
|
|
return h
|
|
}
|
|
func memhash8(p unsafe.Pointer, h uintptr) uintptr {
|
|
return memhash(p, h, 1)
|
|
}
|
|
func memhash16(p unsafe.Pointer, h uintptr) uintptr {
|
|
return memhash(p, h, 2)
|
|
}
|
|
func memhash32(p unsafe.Pointer, h uintptr) uintptr {
|
|
return memhash(p, h, 4)
|
|
}
|
|
func memhash64(p unsafe.Pointer, h uintptr) uintptr {
|
|
return memhash(p, h, 8)
|
|
}
|
|
func memhash128(p unsafe.Pointer, h uintptr) uintptr {
|
|
return memhash(p, h, 16)
|
|
}
|
|
|
|
// memhash_varlen is defined in assembly because it needs access
|
|
// to the closure. It appears here to provide an argument
|
|
// signature for the assembly routine.
|
|
func memhash_varlen(p unsafe.Pointer, h uintptr) uintptr
|
|
|
|
var algarray = [alg_max]typeAlg{
|
|
alg_MEM: {nil, nil}, // not used
|
|
alg_MEM0: {memhash0, memequal0},
|
|
alg_MEM8: {memhash8, memequal8},
|
|
alg_MEM16: {memhash16, memequal16},
|
|
alg_MEM32: {memhash32, memequal32},
|
|
alg_MEM64: {memhash64, memequal64},
|
|
alg_MEM128: {memhash128, memequal128},
|
|
alg_NOEQ: {nil, nil},
|
|
alg_NOEQ0: {nil, nil},
|
|
alg_NOEQ8: {nil, nil},
|
|
alg_NOEQ16: {nil, nil},
|
|
alg_NOEQ32: {nil, nil},
|
|
alg_NOEQ64: {nil, nil},
|
|
alg_NOEQ128: {nil, nil},
|
|
alg_STRING: {strhash, strequal},
|
|
alg_INTER: {interhash, interequal},
|
|
alg_NILINTER: {nilinterhash, nilinterequal},
|
|
alg_SLICE: {nil, nil},
|
|
alg_FLOAT32: {f32hash, f32equal},
|
|
alg_FLOAT64: {f64hash, f64equal},
|
|
alg_CPLX64: {c64hash, c64equal},
|
|
alg_CPLX128: {c128hash, c128equal},
|
|
}
|
|
|
|
var useAeshash bool
|
|
|
|
// in asm_*.s
|
|
func aeshash(p unsafe.Pointer, h, s uintptr) uintptr
|
|
func aeshash32(p unsafe.Pointer, h uintptr) uintptr
|
|
func aeshash64(p unsafe.Pointer, h uintptr) uintptr
|
|
func aeshashstr(p unsafe.Pointer, h uintptr) uintptr
|
|
|
|
func strhash(a unsafe.Pointer, h uintptr) uintptr {
|
|
x := (*stringStruct)(a)
|
|
return memhash(x.str, h, uintptr(x.len))
|
|
}
|
|
|
|
// NOTE: Because NaN != NaN, a map can contain any
|
|
// number of (mostly useless) entries keyed with NaNs.
|
|
// To avoid long hash chains, we assign a random number
|
|
// as the hash value for a NaN.
|
|
|
|
func f32hash(p unsafe.Pointer, h uintptr) uintptr {
|
|
f := *(*float32)(p)
|
|
switch {
|
|
case f == 0:
|
|
return c1 * (c0 ^ h) // +0, -0
|
|
case f != f:
|
|
return c1 * (c0 ^ h ^ uintptr(fastrand1())) // any kind of NaN
|
|
default:
|
|
return memhash(p, h, 4)
|
|
}
|
|
}
|
|
|
|
func f64hash(p unsafe.Pointer, h uintptr) uintptr {
|
|
f := *(*float64)(p)
|
|
switch {
|
|
case f == 0:
|
|
return c1 * (c0 ^ h) // +0, -0
|
|
case f != f:
|
|
return c1 * (c0 ^ h ^ uintptr(fastrand1())) // any kind of NaN
|
|
default:
|
|
return memhash(p, h, 8)
|
|
}
|
|
}
|
|
|
|
func c64hash(p unsafe.Pointer, h uintptr) uintptr {
|
|
x := (*[2]float32)(p)
|
|
return f32hash(unsafe.Pointer(&x[1]), f32hash(unsafe.Pointer(&x[0]), h))
|
|
}
|
|
|
|
func c128hash(p unsafe.Pointer, h uintptr) uintptr {
|
|
x := (*[2]float64)(p)
|
|
return f64hash(unsafe.Pointer(&x[1]), f64hash(unsafe.Pointer(&x[0]), h))
|
|
}
|
|
|
|
func interhash(p unsafe.Pointer, h uintptr) uintptr {
|
|
a := (*iface)(p)
|
|
tab := a.tab
|
|
if tab == nil {
|
|
return h
|
|
}
|
|
t := tab._type
|
|
fn := t.alg.hash
|
|
if fn == nil {
|
|
panic(errorString("hash of unhashable type " + *t._string))
|
|
}
|
|
if isDirectIface(t) {
|
|
return c1 * fn(unsafe.Pointer(&a.data), h^c0)
|
|
} else {
|
|
return c1 * fn(a.data, h^c0)
|
|
}
|
|
}
|
|
|
|
func nilinterhash(p unsafe.Pointer, h uintptr) uintptr {
|
|
a := (*eface)(p)
|
|
t := a._type
|
|
if t == nil {
|
|
return h
|
|
}
|
|
fn := t.alg.hash
|
|
if fn == nil {
|
|
panic(errorString("hash of unhashable type " + *t._string))
|
|
}
|
|
if isDirectIface(t) {
|
|
return c1 * fn(unsafe.Pointer(&a.data), h^c0)
|
|
} else {
|
|
return c1 * fn(a.data, h^c0)
|
|
}
|
|
}
|
|
|
|
func memequal(p, q unsafe.Pointer, size uintptr) bool {
|
|
if p == q {
|
|
return true
|
|
}
|
|
return memeq(p, q, size)
|
|
}
|
|
|
|
func memequal0(p, q unsafe.Pointer) bool {
|
|
return true
|
|
}
|
|
func memequal8(p, q unsafe.Pointer) bool {
|
|
return *(*int8)(p) == *(*int8)(q)
|
|
}
|
|
func memequal16(p, q unsafe.Pointer) bool {
|
|
return *(*int16)(p) == *(*int16)(q)
|
|
}
|
|
func memequal32(p, q unsafe.Pointer) bool {
|
|
return *(*int32)(p) == *(*int32)(q)
|
|
}
|
|
func memequal64(p, q unsafe.Pointer) bool {
|
|
return *(*int64)(p) == *(*int64)(q)
|
|
}
|
|
func memequal128(p, q unsafe.Pointer) bool {
|
|
return *(*[2]int64)(p) == *(*[2]int64)(q)
|
|
}
|
|
func f32equal(p, q unsafe.Pointer) bool {
|
|
return *(*float32)(p) == *(*float32)(q)
|
|
}
|
|
func f64equal(p, q unsafe.Pointer) bool {
|
|
return *(*float64)(p) == *(*float64)(q)
|
|
}
|
|
func c64equal(p, q unsafe.Pointer) bool {
|
|
return *(*complex64)(p) == *(*complex64)(q)
|
|
}
|
|
func c128equal(p, q unsafe.Pointer) bool {
|
|
return *(*complex128)(p) == *(*complex128)(q)
|
|
}
|
|
func strequal(p, q unsafe.Pointer) bool {
|
|
return *(*string)(p) == *(*string)(q)
|
|
}
|
|
func interequal(p, q unsafe.Pointer) bool {
|
|
return ifaceeq(*(*interface {
|
|
f()
|
|
})(p), *(*interface {
|
|
f()
|
|
})(q))
|
|
}
|
|
func nilinterequal(p, q unsafe.Pointer) bool {
|
|
return efaceeq(*(*interface{})(p), *(*interface{})(q))
|
|
}
|
|
func efaceeq(p, q interface{}) bool {
|
|
x := (*eface)(unsafe.Pointer(&p))
|
|
y := (*eface)(unsafe.Pointer(&q))
|
|
t := x._type
|
|
if t != y._type {
|
|
return false
|
|
}
|
|
if t == nil {
|
|
return true
|
|
}
|
|
eq := t.alg.equal
|
|
if eq == nil {
|
|
panic(errorString("comparing uncomparable type " + *t._string))
|
|
}
|
|
if isDirectIface(t) {
|
|
return eq(noescape(unsafe.Pointer(&x.data)), noescape(unsafe.Pointer(&y.data)))
|
|
}
|
|
return eq(x.data, y.data)
|
|
}
|
|
func ifaceeq(p, q interface {
|
|
f()
|
|
}) bool {
|
|
x := (*iface)(unsafe.Pointer(&p))
|
|
y := (*iface)(unsafe.Pointer(&q))
|
|
xtab := x.tab
|
|
if xtab != y.tab {
|
|
return false
|
|
}
|
|
if xtab == nil {
|
|
return true
|
|
}
|
|
t := xtab._type
|
|
eq := t.alg.equal
|
|
if eq == nil {
|
|
panic(errorString("comparing uncomparable type " + *t._string))
|
|
}
|
|
if isDirectIface(t) {
|
|
return eq(noescape(unsafe.Pointer(&x.data)), noescape(unsafe.Pointer(&y.data)))
|
|
}
|
|
return eq(x.data, y.data)
|
|
}
|
|
|
|
// Testing adapters for hash quality tests (see hash_test.go)
|
|
func stringHash(s string, seed uintptr) uintptr {
|
|
return algarray[alg_STRING].hash(noescape(unsafe.Pointer(&s)), seed)
|
|
}
|
|
|
|
func bytesHash(b []byte, seed uintptr) uintptr {
|
|
s := (*slice)(unsafe.Pointer(&b))
|
|
return memhash(s.array, seed, uintptr(s.len))
|
|
}
|
|
|
|
func int32Hash(i uint32, seed uintptr) uintptr {
|
|
return algarray[alg_MEM32].hash(noescape(unsafe.Pointer(&i)), seed)
|
|
}
|
|
|
|
func int64Hash(i uint64, seed uintptr) uintptr {
|
|
return algarray[alg_MEM64].hash(noescape(unsafe.Pointer(&i)), seed)
|
|
}
|
|
|
|
func efaceHash(i interface{}, seed uintptr) uintptr {
|
|
return algarray[alg_NILINTER].hash(noescape(unsafe.Pointer(&i)), seed)
|
|
}
|
|
|
|
func ifaceHash(i interface {
|
|
F()
|
|
}, seed uintptr) uintptr {
|
|
return algarray[alg_INTER].hash(noescape(unsafe.Pointer(&i)), seed)
|
|
}
|
|
|
|
// Testing adapter for memclr
|
|
func memclrBytes(b []byte) {
|
|
s := (*slice)(unsafe.Pointer(&b))
|
|
memclr(s.array, uintptr(s.len))
|
|
}
|
|
|
|
const hashRandomBytes = ptrSize / 4 * 64
|
|
|
|
// used in asm_{386,amd64}.s to seed the hash function
|
|
var aeskeysched [hashRandomBytes]byte
|
|
|
|
// used in hash{32,64}.go to seed the hash function
|
|
var hashkey [4]uintptr
|
|
|
|
func init() {
|
|
// Install aes hash algorithm if we have the instructions we need
|
|
if (GOARCH == "386" || GOARCH == "amd64") &&
|
|
GOOS != "nacl" &&
|
|
cpuid_ecx&(1<<25) != 0 && // aes (aesenc)
|
|
cpuid_ecx&(1<<9) != 0 && // sse3 (pshufb)
|
|
cpuid_ecx&(1<<19) != 0 { // sse4.1 (pinsr{d,q})
|
|
useAeshash = true
|
|
algarray[alg_MEM32].hash = aeshash32
|
|
algarray[alg_MEM64].hash = aeshash64
|
|
algarray[alg_STRING].hash = aeshashstr
|
|
// Initialize with random data so hash collisions will be hard to engineer.
|
|
getRandomData(aeskeysched[:])
|
|
return
|
|
}
|
|
getRandomData((*[len(hashkey) * ptrSize]byte)(unsafe.Pointer(&hashkey))[:])
|
|
hashkey[0] |= 1 // make sure these numbers are odd
|
|
hashkey[1] |= 1
|
|
hashkey[2] |= 1
|
|
hashkey[3] |= 1
|
|
}
|