github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/natives/src/hash/maphash/maphash.go (about) 1 //go:build js 2 // +build js 3 4 package maphash 5 6 import ( 7 _ "unsafe" // for linkname 8 ) 9 10 // hashkey is similar how it is defined in runtime/alg.go for Go 1.19 11 // to be used in hash{32,64}.go to seed the hash function as part of 12 // runtime_memhash. We're using locally defined memhash so it got moved here. 13 var hashkey [3]uint32 14 15 func init() { 16 for i := range hashkey { 17 hashkey[i] = runtime_fastrand() | 1 18 // The `| 1` is to make sure these numbers are odd 19 } 20 } 21 22 //go:linkname runtime_fastrand runtime.fastrand 23 func runtime_fastrand() uint32 24 25 // Bytes uses less efficient equivalent to avoid using unsafe. 26 func Bytes(seed Seed, b []byte) uint64 { 27 var h Hash 28 h.SetSeed(seed) 29 _, _ = h.Write(b) 30 return h.Sum64() 31 } 32 33 // String uses less efficient equivalent to avoid using unsafe. 34 func String(seed Seed, s string) uint64 { 35 var h Hash 36 h.SetSeed(seed) 37 _, _ = h.WriteString(s) 38 return h.Sum64() 39 } 40 41 // rthash is similar to the Go 1.19.13 version 42 // with the call to memhash changed to not use unsafe pointers. 43 func rthash(b []byte, seed uint64) uint64 { 44 if len(b) == 0 { 45 return seed 46 } 47 // The runtime hasher only works on uintptr. Since GopherJS implements a 48 // 32-bit environment, we use two parallel hashers on the lower and upper 32 49 // bits. 50 lo := memhash(b, uint32(seed)) 51 hi := memhash(b, uint32(seed>>32)) 52 return uint64(hi)<<32 | uint64(lo) 53 } 54 55 //gopherjs:purge to remove link using unsafe pointers, use memhash instead. 56 func runtime_memhash() 57 58 // The implementation below is adapted from the upstream runtime/hash32.go 59 // and avoids use of unsafe, which GopherJS doesn't support well and leads to 60 // worse performance. 61 // 62 // Note that this hashing function is not actually used by GopherJS maps, since 63 // we use JS maps instead, but it may be still applicable for use with custom 64 // map types. 65 // 66 // Hashing algorithm inspired by wyhash: 67 // https://github.com/wangyi-fudan/wyhash/blob/ceb019b530e2c1c14d70b79bfa2bc49de7d95bc1/Modern%20Non-Cryptographic%20Hash%20Function%20and%20Pseudorandom%20Number%20Generator.pdf 68 func memhash(p []byte, seed uint32) uintptr { 69 s := len(p) 70 a, b := mix32(uint32(seed), uint32(s)^hashkey[0]) 71 if s == 0 { 72 return uintptr(a ^ b) 73 } 74 for ; s > 8; s -= 8 { 75 a ^= readUnaligned32(p) 76 b ^= readUnaligned32(add(p, 4)) 77 a, b = mix32(a, b) 78 p = add(p, 8) 79 } 80 if s >= 4 { 81 a ^= readUnaligned32(p) 82 b ^= readUnaligned32(add(p, s-4)) 83 } else { 84 t := uint32(p[0]) 85 t |= uint32(add(p, s>>1)[0]) << 8 86 t |= uint32(add(p, s-1)[0]) << 16 87 b ^= t 88 } 89 a, b = mix32(a, b) 90 a, b = mix32(a, b) 91 return uintptr(a ^ b) 92 } 93 94 func add(p []byte, x int) []byte { 95 return p[x:] 96 } 97 98 // Note: These routines perform the read in little endian. 99 func readUnaligned32(p []byte) uint32 { 100 return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 101 } 102 103 func mix32(a, b uint32) (uint32, uint32) { 104 c := uint64(a^uint32(hashkey[1])) * uint64(b^uint32(hashkey[2])) 105 return uint32(c), uint32(c >> 32) 106 } 107 108 /* 109 The following functions were modified in Go 1.17 to improve performance, 110 but at the expense of being unsafe, and thus incompatible with GopherJS. 111 See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/hash/maphash/maphash.go; 112 To compensate, we use a simplified version of each method from Go 1.19.13, 113 similar to Go 1.16's versions, with the call to rthash changed to not use unsafe pointers. 114 115 See upstream issue https://github.com/golang/go/issues/47342 to implement 116 a purego version of this package, which should render this hack (and 117 likely this entire file) obsolete. 118 */ 119 120 // Write is a simplification from Go 1.19 changed to not use unsafe. 121 func (h *Hash) Write(b []byte) (int, error) { 122 size := len(b) 123 if h.n+len(b) > bufSize { 124 h.initSeed() 125 for h.n+len(b) > bufSize { 126 k := copy(h.buf[h.n:], b) 127 h.state.s = rthash(h.buf[:], h.state.s) 128 b = b[k:] 129 h.n = 0 130 } 131 } 132 h.n += copy(h.buf[h.n:], b) 133 return size, nil 134 } 135 136 // WriteString is a simplification from Go 1.19 changed to not use unsafe. 137 func (h *Hash) WriteString(s string) (int, error) { 138 size := len(s) 139 if h.n+len(s) > bufSize { 140 h.initSeed() 141 for h.n+len(s) > bufSize { 142 k := copy(h.buf[h.n:], s) 143 h.state.s = rthash(h.buf[:], h.state.s) 144 s = s[k:] 145 h.n = 0 146 } 147 } 148 h.n += copy(h.buf[h.n:], s) 149 return size, nil 150 } 151 152 // flush is the Go 1.19 version changed to not use unsafe. 153 func (h *Hash) flush() { 154 if h.n != len(h.buf) { 155 panic("maphash: flush of partially full buffer") 156 } 157 h.initSeed() 158 h.state.s = rthash(h.buf[:], h.state.s) 159 h.n = 0 160 } 161 162 // Sum64 is the Go 1.19 version changed to not use unsafe. 163 func (h *Hash) Sum64() uint64 { 164 h.initSeed() 165 return rthash(h.buf[:h.n], h.state.s) 166 }