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