github.com/puzpuzpuz/xsync/v2@v2.5.2-0.20231021165734-92b8269e19a9/hashing.go (about)

     1  //go:build go1.18
     2  // +build go1.18
     3  
     4  package xsync
     5  
     6  import (
     7  	"hash/maphash"
     8  	"reflect"
     9  	"unsafe"
    10  )
    11  
    12  // makeHashFunc creates a fast hash function for the given comparable type.
    13  // The only limitation is that the type should not contain interfaces inside
    14  // based on runtime.typehash.
    15  func makeHashFunc[T comparable]() func(maphash.Seed, T) uint64 {
    16  	var zero T
    17  
    18  	isInterface := reflect.TypeOf(&zero).Elem().Kind() == reflect.Interface
    19  	is64Bit := unsafe.Sizeof(uintptr(0)) == 8
    20  
    21  	if isInterface {
    22  		if is64Bit {
    23  			return func(seed maphash.Seed, value T) uint64 {
    24  				seed64 := *(*uint64)(unsafe.Pointer(&seed))
    25  				iValue := any(value)
    26  				i := (*iface)(unsafe.Pointer(&iValue))
    27  				return uint64(runtime_typehash(i.typ, noescape(i.word), uintptr(seed64)))
    28  			}
    29  		} else {
    30  			return func(seed maphash.Seed, value T) uint64 {
    31  				seed64 := *(*uint64)(unsafe.Pointer(&seed))
    32  				iValue := any(value)
    33  				i := (*iface)(unsafe.Pointer(&iValue))
    34  
    35  				lo := runtime_typehash(i.typ, noescape(i.word), uintptr(seed64))
    36  				hi := runtime_typehash(i.typ, noescape(i.word), uintptr(seed64>>32))
    37  				return uint64(hi)<<32 | uint64(lo)
    38  			}
    39  		}
    40  	} else {
    41  		var iZero any = zero
    42  		i := (*iface)(unsafe.Pointer(&iZero))
    43  
    44  		if is64Bit {
    45  			return func(seed maphash.Seed, value T) uint64 {
    46  				seed64 := *(*uint64)(unsafe.Pointer(&seed))
    47  				return uint64(runtime_typehash(i.typ, noescape(unsafe.Pointer(&value)), uintptr(seed64)))
    48  			}
    49  		} else {
    50  			return func(seed maphash.Seed, value T) uint64 {
    51  				seed64 := *(*uint64)(unsafe.Pointer(&seed))
    52  
    53  				lo := runtime_typehash(i.typ, noescape(unsafe.Pointer(&value)), uintptr(seed64))
    54  				hi := runtime_typehash(i.typ, noescape(unsafe.Pointer(&value)), uintptr(seed64>>32))
    55  				return uint64(hi)<<32 | uint64(lo)
    56  			}
    57  		}
    58  	}
    59  }
    60  
    61  // DRY version of makeHashFunc
    62  func makeHashFuncDRY[T comparable]() func(maphash.Seed, T) uint64 {
    63  	var zero T
    64  
    65  	if reflect.TypeOf(&zero).Elem().Kind() == reflect.Interface {
    66  		return func(seed maphash.Seed, value T) uint64 {
    67  			iValue := any(value)
    68  			i := (*iface)(unsafe.Pointer(&iValue))
    69  			return runtime_typehash64(i.typ, noescape(i.word), seed)
    70  		}
    71  	} else {
    72  		var iZero any = zero
    73  		i := (*iface)(unsafe.Pointer(&iZero))
    74  		return func(seed maphash.Seed, value T) uint64 {
    75  			return runtime_typehash64(i.typ, noescape(unsafe.Pointer(&value)), seed)
    76  		}
    77  	}
    78  }
    79  
    80  func makeHashFuncNative[T comparable]() func(maphash.Seed, T) uint64 {
    81  	hasher := makeHashFuncNativeInternal(make(map[T]struct{}))
    82  
    83  	is64Bit := unsafe.Sizeof(uintptr(0)) == 8
    84  
    85  	if is64Bit {
    86  		return func(seed maphash.Seed, value T) uint64 {
    87  			seed64 := *(*uint64)(unsafe.Pointer(&seed))
    88  			return uint64(hasher(noescape(unsafe.Pointer(&value)), uintptr(seed64)))
    89  		}
    90  	} else {
    91  		return func(seed maphash.Seed, value T) uint64 {
    92  			seed64 := *(*uint64)(unsafe.Pointer(&seed))
    93  			lo := hasher(noescape(unsafe.Pointer(&value)), uintptr(seed64))
    94  			hi := hasher(noescape(unsafe.Pointer(&value)), uintptr(seed64>>32))
    95  			return uint64(hi)<<32 | uint64(lo)
    96  		}
    97  	}
    98  
    99  }
   100  
   101  type nativeHasher func(unsafe.Pointer, uintptr) uintptr
   102  
   103  func makeHashFuncNativeInternal(mapValue any) nativeHasher {
   104  	// go/src/runtime/type.go
   105  	type tflag uint8
   106  	type nameOff int32
   107  	type typeOff int32
   108  
   109  	// go/src/runtime/type.go
   110  	type _type struct {
   111  		size       uintptr
   112  		ptrdata    uintptr
   113  		hash       uint32
   114  		tflag      tflag
   115  		align      uint8
   116  		fieldAlign uint8
   117  		kind       uint8
   118  		equal      func(unsafe.Pointer, unsafe.Pointer) bool
   119  		gcdata     *byte
   120  		str        nameOff
   121  		ptrToThis  typeOff
   122  	}
   123  
   124  	// go/src/runtime/type.go
   125  	type maptype struct {
   126  		typ    _type
   127  		key    *_type
   128  		elem   *_type
   129  		bucket *_type
   130  		// function for hashing keys (ptr to key, seed) -> hash
   131  		hasher     nativeHasher
   132  		keysize    uint8
   133  		elemsize   uint8
   134  		bucketsize uint16
   135  		flags      uint32
   136  	}
   137  
   138  	type mapiface struct {
   139  		typ *maptype
   140  		val uintptr
   141  	}
   142  
   143  	i := (*mapiface)(unsafe.Pointer(&mapValue))
   144  	return i.typ.hasher
   145  }
   146  
   147  // how interface is represented in memory
   148  type iface struct {
   149  	typ  uintptr
   150  	word unsafe.Pointer
   151  }
   152  
   153  // same as runtime_typehash, but always returns a uint64
   154  // see: maphash.rthash function for details
   155  func runtime_typehash64(t uintptr, p unsafe.Pointer, seed maphash.Seed) uint64 {
   156  	seed64 := *(*uint64)(unsafe.Pointer(&seed))
   157  	if unsafe.Sizeof(uintptr(0)) == 8 {
   158  		return uint64(runtime_typehash(t, noescape(p), uintptr(seed64)))
   159  	}
   160  
   161  	lo := runtime_typehash(t, noescape(p), uintptr(seed64))
   162  	hi := runtime_typehash(t, noescape(p), uintptr(seed64>>32))
   163  	return uint64(hi)<<32 | uint64(lo)
   164  }
   165  
   166  //go:nosplit
   167  //go:nocheckptr
   168  func noescape(p unsafe.Pointer) unsafe.Pointer {
   169  	x := uintptr(p)
   170  	return unsafe.Pointer(x ^ 0)
   171  }
   172  
   173  //go:linkname runtime_typehash runtime.typehash
   174  func runtime_typehash(t uintptr, p unsafe.Pointer, h uintptr) uintptr