Skip to content

Commit e45ff9c

Browse files
dgryskideadprogram
authored andcommitted
src/runtime: add per-map hash seeds
1 parent 39805bc commit e45ff9c

File tree

2 files changed

+61
-44
lines changed

2 files changed

+61
-44
lines changed

src/runtime/algorithm.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ func xorshift32(x uint32) uint32 {
2626
// This function is used by hash/maphash.
2727
func memhash(p unsafe.Pointer, seed, s uintptr) uintptr {
2828
if unsafe.Sizeof(uintptr(0)) > 4 {
29-
return seed ^ uintptr(hash64(p, s))
29+
return uintptr(hash64(p, s, seed))
3030
}
31-
return seed ^ uintptr(hash32(p, s))
31+
return uintptr(hash32(p, s, seed))
3232
}
3333

3434
// Get FNV-1a hash of the given memory buffer.
3535
//
3636
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
37-
func hash32(ptr unsafe.Pointer, n uintptr) uint32 {
38-
var result uint32 = 2166136261 // FNV offset basis
37+
func hash32(ptr unsafe.Pointer, n uintptr, seed uintptr) uint32 {
38+
var result uint32 = 2166136261 ^ uint32(seed) // FNV offset basis
3939
for i := uintptr(0); i < n; i++ {
4040
c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i))
4141
result ^= uint32(c) // XOR with byte
@@ -45,8 +45,8 @@ func hash32(ptr unsafe.Pointer, n uintptr) uint32 {
4545
}
4646

4747
// Also a FNV-1a hash.
48-
func hash64(ptr unsafe.Pointer, n uintptr) uint64 {
49-
var result uint64 = 14695981039346656037 // FNV offset basis
48+
func hash64(ptr unsafe.Pointer, n uintptr, seed uintptr) uint64 {
49+
var result uint64 = 14695981039346656037 ^ uint64(seed) // FNV offset basis
5050
for i := uintptr(0); i < n; i++ {
5151
c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i))
5252
result ^= uint64(c) // XOR with byte

src/runtime/hashmap.go

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import (
1313
// The underlying hashmap structure for Go.
1414
type hashmap struct {
1515
buckets unsafe.Pointer // pointer to array of buckets
16+
seed uintptr
1617
count uintptr
1718
keySize uint8 // maybe this can store the key type as well? E.g. keysize == 5 means string?
1819
valueSize uint8
1920
bucketBits uint8
2021
keyEqual func(x, y unsafe.Pointer, n uintptr) bool
21-
keyHash func(key unsafe.Pointer, size uintptr) uint32
22+
keyHash func(key unsafe.Pointer, size, seed uintptr) uint32
2223
}
2324

2425
type hashmapAlgorithm uint8
@@ -74,6 +75,7 @@ func hashmapMake(keySize, valueSize uint8, sizeHint uintptr, alg uint8) *hashmap
7475

7576
return &hashmap{
7677
buckets: buckets,
78+
seed: uintptr(fastrand()),
7779
keySize: keySize,
7880
valueSize: valueSize,
7981
bucketBits: bucketBits,
@@ -96,7 +98,7 @@ func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintpt
9698
}
9799
}
98100

99-
func hashmapKeyHashAlg(alg hashmapAlgorithm) func(key unsafe.Pointer, n uintptr) uint32 {
101+
func hashmapKeyHashAlg(alg hashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
100102
switch alg {
101103
case hashmapAlgorithmBinary:
102104
return hash32
@@ -148,12 +150,14 @@ func hashmapLenUnsafePointer(p unsafe.Pointer) int {
148150
// Set a specified key to a given value. Grow the map if necessary.
149151
//go:nobounds
150152
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) {
151-
tophash := hashmapTopHash(hash)
152-
153153
if hashmapShouldGrow(m) {
154154
hashmapGrow(m)
155+
// seed changed when we grew; rehash key with new seed
156+
hash = m.keyHash(key, uintptr(m.keySize), m.seed)
155157
}
156158

159+
tophash := hashmapTopHash(hash)
160+
157161
numBuckets := uintptr(1) << m.bucketBits
158162
bucketNumber := (uintptr(hash) & (numBuckets - 1))
159163
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
@@ -221,10 +225,10 @@ func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash u
221225
}
222226

223227
func hashmapGrow(m *hashmap) {
224-
225228
// clone map as empty
226229
n := *m
227230
n.count = 0
231+
n.seed = uintptr(fastrand())
228232

229233
// allocate our new buckets twice as big
230234
n.bucketBits = m.bucketBits + 1
@@ -239,7 +243,7 @@ func hashmapGrow(m *hashmap) {
239243
var value = alloc(uintptr(m.valueSize), nil)
240244

241245
for hashmapNext(m, &it, key, value) {
242-
h := m.keyHash(key, uintptr(m.keySize))
246+
h := n.keyHash(key, uintptr(n.keySize), n.seed)
243247
hashmapSet(&n, key, value, h)
244248
}
245249

@@ -386,7 +390,7 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
386390

387391
// Our view of the buckets doesn't match the parent map.
388392
// Look up the key in the new buckets and return that value if it exists
389-
hash := m.keyHash(key, uintptr(m.keySize))
393+
hash := m.keyHash(key, uintptr(m.keySize), m.seed)
390394
ok := hashmapGet(m, key, value, uintptr(m.valueSize), hash)
391395
if !ok {
392396
// doesn't exist in parent map; try next key
@@ -401,10 +405,9 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
401405
}
402406

403407
// Hashmap with plain binary data keys (not containing strings etc.).
404-
405408
func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
406409
// TODO: detect nil map here and throw a better panic message?
407-
hash := hash32(key, uintptr(m.keySize))
410+
hash := hash32(key, uintptr(m.keySize), m.seed)
408411
hashmapSet(m, key, value, hash)
409412
}
410413

@@ -413,15 +416,15 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr)
413416
memzero(value, uintptr(valueSize))
414417
return false
415418
}
416-
hash := hash32(key, uintptr(m.keySize))
419+
hash := hash32(key, uintptr(m.keySize), m.seed)
417420
return hashmapGet(m, key, value, valueSize, hash)
418421
}
419422

420423
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
421424
if m == nil {
422425
return
423426
}
424-
hash := hash32(key, uintptr(m.keySize))
427+
hash := hash32(key, uintptr(m.keySize), m.seed)
425428
hashmapDelete(m, key, hash)
426429
}
427430

@@ -431,28 +434,35 @@ func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool {
431434
return *(*string)(x) == *(*string)(y)
432435
}
433436

434-
func hashmapStringHash(s string) uint32 {
437+
func hashmapStringHash(s string, seed uintptr) uint32 {
435438
_s := (*_string)(unsafe.Pointer(&s))
436-
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
439+
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed)
437440
}
438441

439-
func hashmapStringPtrHash(sptr unsafe.Pointer, size uintptr) uint32 {
442+
func hashmapStringPtrHash(sptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
440443
_s := *(*_string)(sptr)
441-
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
444+
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed)
442445
}
443446

444447
func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
445-
hash := hashmapStringHash(key)
448+
hash := hashmapStringHash(key, m.seed)
446449
hashmapSet(m, unsafe.Pointer(&key), value, hash)
447450
}
448451

449452
func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
450-
hash := hashmapStringHash(key)
453+
if m == nil {
454+
memzero(value, uintptr(valueSize))
455+
return false
456+
}
457+
hash := hashmapStringHash(key, m.seed)
451458
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
452459
}
453460

454461
func hashmapStringDelete(m *hashmap, key string) {
455-
hash := hashmapStringHash(key)
462+
if m == nil {
463+
return
464+
}
465+
hash := hashmapStringHash(key, m.seed)
456466
hashmapDelete(m, unsafe.Pointer(&key), hash)
457467
}
458468

@@ -465,25 +475,25 @@ func hashmapStringDelete(m *hashmap, key string) {
465475
//go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe
466476
func valueInterfaceUnsafe(v reflect.Value) interface{}
467477

468-
func hashmapFloat32Hash(ptr unsafe.Pointer) uint32 {
478+
func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
469479
f := *(*uint32)(ptr)
470480
if f == 0x80000000 {
471481
// convert -0 to 0 for hashing
472482
f = 0
473483
}
474-
return hash32(unsafe.Pointer(&f), 4)
484+
return hash32(unsafe.Pointer(&f), 4, seed)
475485
}
476486

477-
func hashmapFloat64Hash(ptr unsafe.Pointer) uint32 {
487+
func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
478488
f := *(*uint64)(ptr)
479489
if f == 0x8000000000000000 {
480490
// convert -0 to 0 for hashing
481491
f = 0
482492
}
483-
return hash32(unsafe.Pointer(&f), 8)
493+
return hash32(unsafe.Pointer(&f), 8, seed)
484494
}
485495

486-
func hashmapInterfaceHash(itf interface{}) uint32 {
496+
func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
487497
x := reflect.ValueOf(itf)
488498
if x.RawType() == 0 {
489499
return 0 // nil interface
@@ -498,41 +508,41 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
498508

499509
switch x.RawType().Kind() {
500510
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
501-
return hash32(ptr, x.RawType().Size())
511+
return hash32(ptr, x.RawType().Size(), seed)
502512
case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
503-
return hash32(ptr, x.RawType().Size())
513+
return hash32(ptr, x.RawType().Size(), seed)
504514
case reflect.Float32:
505515
// It should be possible to just has the contents. However, NaN != NaN
506516
// so if you're using lots of NaNs as map keys (you shouldn't) then hash
507517
// time may become exponential. To fix that, it would be better to
508518
// return a random number instead:
509519
// https://research.swtch.com/randhash
510-
return hashmapFloat32Hash(ptr)
520+
return hashmapFloat32Hash(ptr, seed)
511521
case reflect.Float64:
512-
return hashmapFloat64Hash(ptr)
522+
return hashmapFloat64Hash(ptr, seed)
513523
case reflect.Complex64:
514524
rptr, iptr := ptr, unsafe.Pointer(uintptr(ptr)+4)
515-
return hashmapFloat32Hash(rptr) ^ hashmapFloat32Hash(iptr)
525+
return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed)
516526
case reflect.Complex128:
517527
rptr, iptr := ptr, unsafe.Pointer(uintptr(ptr)+8)
518-
return hashmapFloat64Hash(rptr) ^ hashmapFloat64Hash(iptr)
528+
return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed)
519529
case reflect.String:
520-
return hashmapStringHash(x.String())
530+
return hashmapStringHash(x.String(), seed)
521531
case reflect.Chan, reflect.Ptr, reflect.UnsafePointer:
522532
// It might seem better to just return the pointer, but that won't
523533
// result in an evenly distributed hashmap. Instead, hash the pointer
524534
// like most other types.
525-
return hash32(ptr, x.RawType().Size())
535+
return hash32(ptr, x.RawType().Size(), seed)
526536
case reflect.Array:
527537
var hash uint32
528538
for i := 0; i < x.Len(); i++ {
529-
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)))
539+
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed)
530540
}
531541
return hash
532542
case reflect.Struct:
533543
var hash uint32
534544
for i := 0; i < x.NumField(); i++ {
535-
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)))
545+
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed)
536546
}
537547
return hash
538548
default:
@@ -541,26 +551,33 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
541551
}
542552
}
543553

544-
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr) uint32 {
554+
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
545555
_i := *(*_interface)(iptr)
546-
return hashmapInterfaceHash(_i)
556+
return hashmapInterfaceHash(_i, seed)
547557
}
548558

549559
func hashmapInterfaceEqual(x, y unsafe.Pointer, n uintptr) bool {
550560
return *(*interface{})(x) == *(*interface{})(y)
551561
}
552562

553563
func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) {
554-
hash := hashmapInterfaceHash(key)
564+
hash := hashmapInterfaceHash(key, m.seed)
555565
hashmapSet(m, unsafe.Pointer(&key), value, hash)
556566
}
557567

558568
func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool {
559-
hash := hashmapInterfaceHash(key)
569+
if m == nil {
570+
memzero(value, uintptr(valueSize))
571+
return false
572+
}
573+
hash := hashmapInterfaceHash(key, m.seed)
560574
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
561575
}
562576

563577
func hashmapInterfaceDelete(m *hashmap, key interface{}) {
564-
hash := hashmapInterfaceHash(key)
578+
if m == nil {
579+
return
580+
}
581+
hash := hashmapInterfaceHash(key, m.seed)
565582
hashmapDelete(m, unsafe.Pointer(&key), hash)
566583
}

0 commit comments

Comments
 (0)