github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/internal/typeparams/map.go (about) 1 package typeparams 2 3 import ( 4 "go/types" 5 "sync" 6 7 "golang.org/x/tools/go/types/typeutil" 8 ) 9 10 type ( 11 mapEntry[V any] struct { 12 key Instance 13 value V 14 } 15 mapBucket[V any] []*mapEntry[V] 16 mapBuckets[V any] map[uint32]mapBucket[V] 17 ) 18 19 // InstanceMap implements a map-like data structure keyed by instances. 20 // 21 // Zero value is an equivalent of an empty map. Methods are not thread-safe. 22 // 23 // Since Instance contains a slice and is not comparable, it can not be used as 24 // a regular map key, but we can compare its fields manually. When comparing 25 // instance equality, objects are compared by pointer equality, and type 26 // arguments with types.Identical(). To reduce access complexity, we bucket 27 // entries by a combined hash of type args. This type is generally inspired by 28 // typeutil.Map. 29 type InstanceMap[V any] struct { 30 bootstrap sync.Once 31 data map[types.Object]mapBuckets[V] 32 len int 33 hasher typeutil.Hasher 34 zero V 35 } 36 37 func (im *InstanceMap[V]) init() { 38 im.bootstrap.Do(func() { 39 im.data = map[types.Object]mapBuckets[V]{} 40 im.hasher = typeutil.MakeHasher() 41 }) 42 } 43 44 func (im *InstanceMap[V]) get(key Instance) (V, bool) { 45 im.init() 46 47 buckets, ok := im.data[key.Object] 48 if !ok { 49 return im.zero, false 50 } 51 bucket := buckets[typeHash(im.hasher, key.TArgs...)] 52 if len(bucket) == 0 { 53 return im.zero, false 54 } 55 56 for _, candidate := range bucket { 57 if typeArgsEq(candidate.key.TArgs, key.TArgs) { 58 return candidate.value, true 59 } 60 } 61 return im.zero, false 62 } 63 64 // Get returns the stored value for the provided key. If the key is missing from 65 // the map, zero value is returned. 66 func (im *InstanceMap[V]) Get(key Instance) V { 67 val, _ := im.get(key) 68 return val 69 } 70 71 // Has returns true if the given key is present in the map. 72 func (im *InstanceMap[V]) Has(key Instance) bool { 73 _, ok := im.get(key) 74 return ok 75 } 76 77 // Set new value for the key in the map. Returns the previous value that was 78 // stored in the map, or zero value if the key wasn't present before. 79 func (im *InstanceMap[V]) Set(key Instance, value V) (old V) { 80 im.init() 81 82 if _, ok := im.data[key.Object]; !ok { 83 im.data[key.Object] = mapBuckets[V]{} 84 } 85 bucketID := typeHash(im.hasher, key.TArgs...) 86 87 // If there is already an identical key in the map, override the entry value. 88 for _, candidate := range im.data[key.Object][bucketID] { 89 if typeArgsEq(candidate.key.TArgs, key.TArgs) { 90 old = candidate.value 91 candidate.value = value 92 return old 93 } 94 } 95 96 // Otherwise append a new entry. 97 im.data[key.Object][bucketID] = append(im.data[key.Object][bucketID], &mapEntry[V]{ 98 key: key, 99 value: value, 100 }) 101 im.len++ 102 return im.zero 103 } 104 105 // Len returns the number of elements in the map. 106 func (im *InstanceMap[V]) Len() int { 107 return im.len 108 } 109 110 // typeHash returns a combined hash of several types. 111 // 112 // Provided hasher is used to compute hashes of individual types, which are 113 // xor'ed together. Xor preserves bit distribution property, so the combined 114 // hash should be as good for bucketing, as the original. 115 func typeHash(hasher typeutil.Hasher, types ...types.Type) uint32 { 116 var hash uint32 117 for _, typ := range types { 118 hash ^= hasher.Hash(typ) 119 } 120 return hash 121 } 122 123 // typeArgsEq returns if both lists of type arguments are identical. 124 func typeArgsEq(a, b []types.Type) bool { 125 if len(a) != len(b) { 126 return false 127 } 128 for i := range a { 129 if !types.Identical(a[i], b[i]) { 130 return false 131 } 132 } 133 134 return true 135 }