github.com/arr-ai/hash@v0.8.0/alg.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package hash
     6  
     7  import (
     8  	"crypto/rand"
     9  	"fmt"
    10  	"runtime"
    11  	"unsafe"
    12  
    13  	"golang.org/x/sys/cpu"
    14  )
    15  
    16  const (
    17  	c0 = (8-unsafe.Sizeof(uintptr(0)))/4*2860486313 +
    18  		(unsafe.Sizeof(uintptr(0))-4)/4*(33054211828000289&(1<<unsafe.Sizeof(uintptr(0))-1))
    19  	c1 = (8-unsafe.Sizeof(uintptr(0)))/4*3267000013 +
    20  		(unsafe.Sizeof(uintptr(0))-4)/4*(23344194077549503&(1<<unsafe.Sizeof(uintptr(0))-1))
    21  )
    22  
    23  // type algorithms - known to compiler
    24  const (
    25  	algNOEQ = iota
    26  	algMEM0
    27  	algMEM8
    28  	algMEM16
    29  	algMEM32
    30  	algMEM64
    31  	algMEM128
    32  	algSTRING
    33  	algFLOAT32
    34  	algFLOAT64
    35  	algCPLX64
    36  	algCPLX128
    37  	algMAX
    38  	algPTR  = algMEM32 + (algMEM64-algMEM32)*(unsafe.Sizeof(uintptr(0))/8)
    39  	algINT  = algMEM32 + (algMEM64-algMEM32)*(unsafe.Sizeof(0)/8)
    40  	algUINT = algMEM32 + (algMEM64-algMEM32)*(unsafe.Sizeof(uint(0))/8)
    41  )
    42  
    43  type hashFunc func(unsafe.Pointer, uintptr) uintptr
    44  
    45  func memhash0(p unsafe.Pointer, h uintptr) uintptr {
    46  	return h
    47  }
    48  
    49  func memhash8(p unsafe.Pointer, h uintptr) uintptr {
    50  	return memhash(p, h, 1)
    51  }
    52  
    53  func memhash16(p unsafe.Pointer, h uintptr) uintptr {
    54  	return memhash(p, h, 2)
    55  }
    56  
    57  func memhash128(p unsafe.Pointer, h uintptr) uintptr {
    58  	return memhash(p, h, 16)
    59  }
    60  
    61  var algarray = [algMAX]hashFunc{
    62  	algNOEQ:    nil,
    63  	algMEM0:    memhash0,
    64  	algMEM8:    memhash8,
    65  	algMEM16:   memhash16,
    66  	algMEM32:   memhash32,
    67  	algMEM64:   memhash64,
    68  	algMEM128:  memhash128,
    69  	algSTRING:  strhash,
    70  	algFLOAT32: f32hash,
    71  	algFLOAT64: f64hash,
    72  	algCPLX64:  c64hash,
    73  	algCPLX128: c128hash,
    74  }
    75  
    76  var useAeshash bool
    77  
    78  // in asm_*.s
    79  func aeshash(p unsafe.Pointer, h, s uintptr) uintptr
    80  func aeshash32(p unsafe.Pointer, h uintptr) uintptr
    81  func aeshash64(p unsafe.Pointer, h uintptr) uintptr
    82  func aeshashstr(p unsafe.Pointer, h uintptr) uintptr
    83  
    84  type stringStruct struct {
    85  	str unsafe.Pointer
    86  	len int
    87  }
    88  
    89  func strhash(a unsafe.Pointer, h uintptr) uintptr {
    90  	x := (*stringStruct)(a)
    91  	return memhash(x.str, h, uintptr(x.len))
    92  }
    93  
    94  // NOTE: Because NaN != NaN, a map can contain any
    95  // number of (mostly useless) entries keyed with NaNs.
    96  // To avoid long hash chains, we assign a random number
    97  // as the hash value for a NaN.
    98  
    99  func f32hash(p unsafe.Pointer, h uintptr) uintptr {
   100  	f := *(*float32)(p)
   101  	switch {
   102  	case f == 0:
   103  		return c1 * (c0 ^ h) // +0, -0
   104  	case f != f:
   105  		return c1 * (c0 ^ h ^ uintptr(fastrand())) // any kind of NaN
   106  	default:
   107  		return memhash(p, h, 4)
   108  	}
   109  }
   110  
   111  func f64hash(p unsafe.Pointer, h uintptr) uintptr {
   112  	f := *(*float64)(p)
   113  	switch {
   114  	case f == 0:
   115  		return c1 * (c0 ^ h) // +0, -0
   116  	case f != f:
   117  		return c1 * (c0 ^ h ^ uintptr(fastrand())) // any kind of NaN
   118  	default:
   119  		return memhash(p, h, 8)
   120  	}
   121  }
   122  
   123  func c64hash(p unsafe.Pointer, h uintptr) uintptr {
   124  	x := (*[2]float32)(p)
   125  	return f32hash(unsafe.Pointer(&x[1]), f32hash(unsafe.Pointer(&x[0]), h))
   126  }
   127  
   128  func c128hash(p unsafe.Pointer, h uintptr) uintptr {
   129  	x := (*[2]float64)(p)
   130  	return f64hash(unsafe.Pointer(&x[1]), f64hash(unsafe.Pointer(&x[0]), h))
   131  }
   132  
   133  const hashRandomBytes = unsafe.Sizeof(uintptr(0)) / 4 * 64
   134  
   135  //go:nosplit
   136  func getRandomData(r []byte) {
   137  	if _, err := rand.Read(r); err != nil {
   138  		panic(err)
   139  	}
   140  }
   141  
   142  // used in asm_{386,amd64,arm64}.s to seed the hash function
   143  var aeskeysched [hashRandomBytes]byte
   144  
   145  // used in hash{32,64}.go to seed the hash function
   146  var hashkey [4]uintptr
   147  
   148  // GetSeeds returns the internal seeds used for computing hashes.
   149  func GetSeeds() (a []byte, h []uintptr) {
   150  	if useAeshash {
   151  		return aeskeysched[:], nil
   152  	}
   153  	return nil, hashkey[:]
   154  }
   155  
   156  // SetSeeds sets the internal seeds used for computing hashes.
   157  func SetSeeds(a []byte, h []uintptr) error {
   158  	if useAeshash {
   159  		if len(a) != len(aeskeysched) {
   160  			return fmt.Errorf("SetSeeds: aeskeysched: %d != %d", len(a), len(aeskeysched)) //nolint:golint
   161  		}
   162  		copy(aeskeysched[:], a)
   163  		return nil
   164  	}
   165  	if len(h) != len(hashkey) {
   166  		return fmt.Errorf("SetSeeds: hashkey: %d != %d", len(h), len(hashkey)) //nolint:golint
   167  	}
   168  	copy(hashkey[:], h)
   169  	return nil
   170  }
   171  
   172  var _ = func() (_ struct{}) {
   173  	// Install AES hash algorithms if the instructions needed are present.
   174  	if (runtime.GOARCH == "386" || runtime.GOARCH == "amd64") && runtime.GOOS != "nacl" &&
   175  		cpu.X86.HasAES && // AESENC
   176  		cpu.X86.HasSSSE3 && // PSHUFB
   177  		cpu.X86.HasSSE41 { // PINSR{D,Q}
   178  		initAlgAES()
   179  	} else if runtime.GOARCH == "arm64" && cpu.ARM64.HasAES {
   180  		initAlgAES()
   181  	} else {
   182  		getRandomData((*[len(hashkey) * int(unsafe.Sizeof(uintptr(0)))]byte)(unsafe.Pointer(&hashkey))[:])
   183  		hashkey[0] |= 1 // make sure these numbers are odd
   184  		hashkey[1] |= 1
   185  		hashkey[2] |= 1
   186  		hashkey[3] |= 1
   187  	}
   188  	return
   189  }()
   190  
   191  func initAlgAES() {
   192  	if runtime.GOOS == "aix" {
   193  		// runtime.algarray is immutable on AIX: see cmd/link/internal/ld/xcoff.go
   194  		return
   195  	}
   196  	useAeshash = true
   197  	algarray[algMEM32] = aeshash32
   198  	algarray[algMEM64] = aeshash64
   199  	algarray[algSTRING] = aeshashstr
   200  	// Initialize with random data so hash collisions will be hard to engineer.
   201  	getRandomData(aeskeysched[:])
   202  }
   203  
   204  // Note: These routines perform the read with an native endianness.
   205  func readUnaligned32(p unsafe.Pointer) uint32 {
   206  	q := (*[4]byte)(p)
   207  	if bigEndian {
   208  		return uint32(q[3]) | uint32(q[2])<<8 | uint32(q[1])<<16 | uint32(q[0])<<24
   209  	}
   210  	return uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24
   211  }
   212  
   213  func readUnaligned64(p unsafe.Pointer) uint64 {
   214  	q := (*[8]byte)(p)
   215  	if bigEndian {
   216  		return uint64(q[7]) | uint64(q[6])<<8 | uint64(q[5])<<16 | uint64(q[4])<<24 |
   217  			uint64(q[3])<<32 | uint64(q[2])<<40 | uint64(q[1])<<48 | uint64(q[0])<<56
   218  	}
   219  	return uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 |
   220  		uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56
   221  }