github.com/aykevl/tinygo@v0.5.0/src/runtime/hashmap.go (about)

     1  package runtime
     2  
     3  // This is a hashmap implementation for the map[T]T type.
     4  // It is very rougly based on the implementation of the Go hashmap:
     5  //
     6  //     https://golang.org/src/runtime/map.go
     7  
     8  import (
     9  	"unsafe"
    10  )
    11  
    12  // The underlying hashmap structure for Go.
    13  type hashmap struct {
    14  	next       *hashmap       // hashmap after evacuate (for iterators)
    15  	buckets    unsafe.Pointer // pointer to array of buckets
    16  	count      uintptr
    17  	keySize    uint8 // maybe this can store the key type as well? E.g. keysize == 5 means string?
    18  	valueSize  uint8
    19  	bucketBits uint8
    20  }
    21  
    22  // A hashmap bucket. A bucket is a container of 8 key/value pairs: first the
    23  // following two entries, then the 8 keys, then the 8 values. This somewhat odd
    24  // ordering is to make sure the keys and values are well aligned when one of
    25  // them is smaller than the system word size.
    26  type hashmapBucket struct {
    27  	tophash [8]uint8
    28  	next    *hashmapBucket // next bucket (if there are more than 8 in a chain)
    29  	// Followed by the actual keys, and then the actual values. These are
    30  	// allocated but as they're of variable size they can't be shown here.
    31  }
    32  
    33  type hashmapIterator struct {
    34  	bucketNumber uintptr
    35  	bucket       *hashmapBucket
    36  	bucketIndex  uint8
    37  }
    38  
    39  // Get FNV-1a hash of this key.
    40  //
    41  // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
    42  func hashmapHash(ptr unsafe.Pointer, n uintptr) uint32 {
    43  	var result uint32 = 2166136261 // FNV offset basis
    44  	for i := uintptr(0); i < n; i++ {
    45  		c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i))
    46  		result ^= uint32(c) // XOR with byte
    47  		result *= 16777619  // FNV prime
    48  	}
    49  	return result
    50  }
    51  
    52  // Get the topmost 8 bits of the hash, without using a special value (like 0).
    53  func hashmapTopHash(hash uint32) uint8 {
    54  	tophash := uint8(hash >> 24)
    55  	if tophash < 1 {
    56  		// 0 means empty slot, so make it bigger.
    57  		tophash += 1
    58  	}
    59  	return tophash
    60  }
    61  
    62  // Create a new hashmap with the given keySize and valueSize.
    63  func hashmapMake(keySize, valueSize uint8) *hashmap {
    64  	bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
    65  	bucket := alloc(bucketBufSize)
    66  	return &hashmap{
    67  		buckets:    bucket,
    68  		keySize:    keySize,
    69  		valueSize:  valueSize,
    70  		bucketBits: 0,
    71  	}
    72  }
    73  
    74  // Return the number of entries in this hashmap, called from the len builtin.
    75  // A nil hashmap is defined as having length 0.
    76  func hashmapLen(m *hashmap) int {
    77  	if m == nil {
    78  		return 0
    79  	}
    80  	return int(m.count)
    81  }
    82  
    83  // Set a specified key to a given value. Grow the map if necessary.
    84  //go:nobounds
    85  func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
    86  	numBuckets := uintptr(1) << m.bucketBits
    87  	bucketNumber := (uintptr(hash) & (numBuckets - 1))
    88  	bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
    89  	bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
    90  	bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
    91  
    92  	tophash := hashmapTopHash(hash)
    93  
    94  	// See whether the key already exists somewhere.
    95  	var emptySlotKey unsafe.Pointer
    96  	var emptySlotValue unsafe.Pointer
    97  	var emptySlotTophash *byte
    98  	for bucket != nil {
    99  		for i := uintptr(0); i < 8; i++ {
   100  			slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
   101  			slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
   102  			slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
   103  			slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
   104  			if bucket.tophash[i] == 0 && emptySlotKey == nil {
   105  				// Found an empty slot, store it for if we couldn't find an
   106  				// existing slot.
   107  				emptySlotKey = slotKey
   108  				emptySlotValue = slotValue
   109  				emptySlotTophash = &bucket.tophash[i]
   110  			}
   111  			if bucket.tophash[i] == tophash {
   112  				// Could be an existing value that's the same.
   113  				if keyEqual(key, slotKey, uintptr(m.keySize)) {
   114  					// found same key, replace it
   115  					memcpy(slotValue, value, uintptr(m.valueSize))
   116  					return
   117  				}
   118  			}
   119  		}
   120  		bucket = bucket.next
   121  	}
   122  	if emptySlotKey != nil {
   123  		m.count++
   124  		memcpy(emptySlotKey, key, uintptr(m.keySize))
   125  		memcpy(emptySlotValue, value, uintptr(m.valueSize))
   126  		*emptySlotTophash = tophash
   127  		return
   128  	}
   129  	panic("todo: hashmap: grow bucket")
   130  }
   131  
   132  // Get the value of a specified key, or zero the value if not found.
   133  //go:nobounds
   134  func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) bool {
   135  	numBuckets := uintptr(1) << m.bucketBits
   136  	bucketNumber := (uintptr(hash) & (numBuckets - 1))
   137  	bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
   138  	bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
   139  	bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
   140  
   141  	tophash := uint8(hash >> 24)
   142  	if tophash < 1 {
   143  		// 0 means empty slot, so make it bigger.
   144  		tophash += 1
   145  	}
   146  
   147  	// Try to find the key.
   148  	for bucket != nil {
   149  		for i := uintptr(0); i < 8; i++ {
   150  			slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
   151  			slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
   152  			slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
   153  			slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
   154  			if bucket.tophash[i] == tophash {
   155  				// This could be the key we're looking for.
   156  				if keyEqual(key, slotKey, uintptr(m.keySize)) {
   157  					// Found the key, copy it.
   158  					memcpy(value, slotValue, uintptr(m.valueSize))
   159  					return true
   160  				}
   161  			}
   162  		}
   163  		bucket = bucket.next
   164  	}
   165  
   166  	// Did not find the key.
   167  	memzero(value, uintptr(m.valueSize))
   168  	return false
   169  }
   170  
   171  // Delete a given key from the map. No-op when the key does not exist in the
   172  // map.
   173  //go:nobounds
   174  func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
   175  	numBuckets := uintptr(1) << m.bucketBits
   176  	bucketNumber := (uintptr(hash) & (numBuckets - 1))
   177  	bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
   178  	bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
   179  	bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
   180  
   181  	tophash := uint8(hash >> 24)
   182  	if tophash < 1 {
   183  		// 0 means empty slot, so make it bigger.
   184  		tophash += 1
   185  	}
   186  
   187  	// Try to find the key.
   188  	for bucket != nil {
   189  		for i := uintptr(0); i < 8; i++ {
   190  			slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
   191  			slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
   192  			if bucket.tophash[i] == tophash {
   193  				// This could be the key we're looking for.
   194  				if keyEqual(key, slotKey, uintptr(m.keySize)) {
   195  					// Found the key, delete it.
   196  					bucket.tophash[i] = 0
   197  					m.count--
   198  					return
   199  				}
   200  			}
   201  		}
   202  		bucket = bucket.next
   203  	}
   204  }
   205  
   206  // Iterate over a hashmap.
   207  //go:nobounds
   208  func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool {
   209  	numBuckets := uintptr(1) << m.bucketBits
   210  	for {
   211  		if it.bucketIndex >= 8 {
   212  			// end of bucket, move to the next in the chain
   213  			it.bucketIndex = 0
   214  			it.bucket = it.bucket.next
   215  		}
   216  		if it.bucket == nil {
   217  			if it.bucketNumber >= numBuckets {
   218  				// went through all buckets
   219  				return false
   220  			}
   221  			bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
   222  			bucketAddr := uintptr(m.buckets) + bucketSize*it.bucketNumber
   223  			it.bucket = (*hashmapBucket)(unsafe.Pointer(bucketAddr))
   224  			it.bucketNumber++ // next bucket
   225  		}
   226  		if it.bucket.tophash[it.bucketIndex] == 0 {
   227  			// slot is empty - move on
   228  			it.bucketIndex++
   229  			continue
   230  		}
   231  
   232  		bucketAddr := uintptr(unsafe.Pointer(it.bucket))
   233  		slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(it.bucketIndex)
   234  		slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
   235  		slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(it.bucketIndex)
   236  		slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
   237  		memcpy(key, slotKey, uintptr(m.keySize))
   238  		memcpy(value, slotValue, uintptr(m.valueSize))
   239  		it.bucketIndex++
   240  
   241  		return true
   242  	}
   243  }
   244  
   245  // Hashmap with plain binary data keys (not containing strings etc.).
   246  
   247  func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
   248  	hash := hashmapHash(key, uintptr(m.keySize))
   249  	hashmapSet(m, key, value, hash, memequal)
   250  }
   251  
   252  func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer) bool {
   253  	hash := hashmapHash(key, uintptr(m.keySize))
   254  	return hashmapGet(m, key, value, hash, memequal)
   255  }
   256  
   257  func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
   258  	hash := hashmapHash(key, uintptr(m.keySize))
   259  	hashmapDelete(m, key, hash, memequal)
   260  }
   261  
   262  // Hashmap with string keys (a common case).
   263  
   264  func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool {
   265  	return *(*string)(x) == *(*string)(y)
   266  }
   267  
   268  func hashmapStringHash(s string) uint32 {
   269  	_s := (*_string)(unsafe.Pointer(&s))
   270  	return hashmapHash(unsafe.Pointer(_s.ptr), uintptr(_s.length))
   271  }
   272  
   273  func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
   274  	hash := hashmapStringHash(key)
   275  	hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
   276  }
   277  
   278  func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer) bool {
   279  	hash := hashmapStringHash(key)
   280  	return hashmapGet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
   281  }
   282  
   283  func hashmapStringDelete(m *hashmap, key string) {
   284  	hash := hashmapStringHash(key)
   285  	hashmapDelete(m, unsafe.Pointer(&key), hash, hashmapStringEqual)
   286  }