|
| 1 | +//go:build !go1.24 |
| 2 | + |
| 3 | +package maphash |
| 4 | + |
| 5 | +import "unsafe" |
| 6 | + |
| 7 | +func Comparable[T comparable](s Seed, v T) uint64 { |
| 8 | + return comparableHash(*(*seedTyp)(unsafe.Pointer(&s)), v) |
| 9 | +} |
| 10 | + |
| 11 | +func comparableHash[T comparable](seed seedTyp, v T) uint64 { |
| 12 | + s := seed.s |
| 13 | + var m map[T]struct{} |
| 14 | + mTyp := iTypeOf(m) |
| 15 | + var hasher func(unsafe.Pointer, uintptr) uintptr |
| 16 | + hasher = (*iMapType)(unsafe.Pointer(mTyp)).Hasher |
| 17 | + |
| 18 | + p := escape(unsafe.Pointer(&v)) |
| 19 | + |
| 20 | + if ptrSize == 8 { |
| 21 | + return uint64(hasher(p, uintptr(s))) |
| 22 | + } |
| 23 | + lo := hasher(p, uintptr(s)) |
| 24 | + hi := hasher(p, uintptr(s>>32)) |
| 25 | + return uint64(hi)<<32 | uint64(lo) |
| 26 | +} |
| 27 | + |
| 28 | +// WriteComparable adds x to the data hashed by h. |
| 29 | +func WriteComparable[T comparable](h *Hash, x T) { |
| 30 | + // writeComparable (not in purego mode) directly operates on h.state |
| 31 | + // without using h.buf. Mix in the buffer length so it won't |
| 32 | + // commute with a buffered write, which either changes h.n or changes |
| 33 | + // h.state. |
| 34 | + hash := (*hashTyp)(unsafe.Pointer(h)) |
| 35 | + if hash.n != 0 { |
| 36 | + hash.state.s = comparableHash(hash.state, hash.n) |
| 37 | + } |
| 38 | + hash.state.s = comparableHash(hash.state, x) |
| 39 | +} |
| 40 | + |
| 41 | +// go/src/hash/maphash/maphash.go |
| 42 | +type hashTyp struct { |
| 43 | + _ [0]func() // not comparable |
| 44 | + seed seedTyp // initial seed used for this hash |
| 45 | + state seedTyp // current hash of all flushed bytes |
| 46 | + buf [128]byte // unflushed byte buffer |
| 47 | + n int // number of unflushed bytes |
| 48 | +} |
| 49 | + |
| 50 | +type seedTyp struct { |
| 51 | + s uint64 |
| 52 | +} |
| 53 | + |
| 54 | +type iTFlag uint8 |
| 55 | +type iKind uint8 |
| 56 | +type iNameOff int32 |
| 57 | + |
| 58 | +// TypeOff is the offset to a type from moduledata.types. See resolveTypeOff in runtime. |
| 59 | +type iTypeOff int32 |
| 60 | + |
| 61 | +type iType struct { |
| 62 | + Size_ uintptr |
| 63 | + PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers |
| 64 | + Hash uint32 // hash of type; avoids computation in hash tables |
| 65 | + TFlag iTFlag // extra type information flags |
| 66 | + Align_ uint8 // alignment of variable with this type |
| 67 | + FieldAlign_ uint8 // alignment of struct field with this type |
| 68 | + Kind_ iKind // enumeration for C |
| 69 | + // function for comparing objects of this type |
| 70 | + // (ptr to object A, ptr to object B) -> ==? |
| 71 | + Equal func(unsafe.Pointer, unsafe.Pointer) bool |
| 72 | + // GCData stores the GC type data for the garbage collector. |
| 73 | + // Normally, GCData points to a bitmask that describes the |
| 74 | + // ptr/nonptr fields of the type. The bitmask will have at |
| 75 | + // least PtrBytes/ptrSize bits. |
| 76 | + // If the TFlagGCMaskOnDemand bit is set, GCData is instead a |
| 77 | + // **byte and the pointer to the bitmask is one dereference away. |
| 78 | + // The runtime will build the bitmask if needed. |
| 79 | + // (See runtime/type.go:getGCMask.) |
| 80 | + // Note: multiple types may have the same value of GCData, |
| 81 | + // including when TFlagGCMaskOnDemand is set. The types will, of course, |
| 82 | + // have the same pointer layout (but not necessarily the same size). |
| 83 | + GCData *byte |
| 84 | + Str iNameOff // string form |
| 85 | + PtrToThis iTypeOff // type for pointer to this type, may be zero |
| 86 | +} |
| 87 | + |
| 88 | +type iMapType struct { |
| 89 | + iType |
| 90 | + Key *iType |
| 91 | + Elem *iType |
| 92 | + Group *iType // internal type representing a slot group |
| 93 | + // function for hashing keys (ptr to key, seed) -> hash |
| 94 | + Hasher func(unsafe.Pointer, uintptr) uintptr |
| 95 | +} |
| 96 | + |
| 97 | +func iTypeOf(a any) *iType { |
| 98 | + eface := *(*iEmptyInterface)(unsafe.Pointer(&a)) |
| 99 | + // Types are either static (for compiler-created types) or |
| 100 | + // heap-allocated but always reachable (for reflection-created |
| 101 | + // types, held in the central map). So there is no need to |
| 102 | + // escape types. noescape here help avoid unnecessary escape |
| 103 | + // of v. |
| 104 | + return (*iType)(noescape(unsafe.Pointer(eface.Type))) |
| 105 | +} |
| 106 | + |
| 107 | +type iEmptyInterface struct { |
| 108 | + Type *iType |
| 109 | + Data unsafe.Pointer |
| 110 | +} |
| 111 | + |
| 112 | +// noescape hides a pointer from escape analysis. noescape is |
| 113 | +// the identity function but escape analysis doesn't think the |
| 114 | +// output depends on the input. noescape is inlined and currently |
| 115 | +// compiles down to zero instructions. |
| 116 | +// USE CAREFULLY! |
| 117 | +// |
| 118 | +// nolint:all |
| 119 | +// |
| 120 | +//go:nosplit |
| 121 | +//goland:noinspection ALL |
| 122 | +func noescape(p unsafe.Pointer) unsafe.Pointer { |
| 123 | + x := uintptr(p) |
| 124 | + return unsafe.Pointer(x ^ 0) |
| 125 | +} |
| 126 | + |
| 127 | +var alwaysFalse bool |
| 128 | +var escapeSink any |
| 129 | + |
| 130 | +// escape forces any pointers in x to escape to the heap. |
| 131 | +func escape[T any](x T) T { |
| 132 | + if alwaysFalse { |
| 133 | + escapeSink = x |
| 134 | + } |
| 135 | + return x |
| 136 | +} |
| 137 | + |
| 138 | +// ptrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0)) but as an ideal constant. |
| 139 | +// It is also the size of the machine's native word size (that is, 4 on 32-bit systems, 8 on 64-bit). |
| 140 | +const ptrSize = 4 << (^uintptr(0) >> 63) |
0 commit comments