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  }