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  }