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 }