github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/hashtable.go (about) 1 package runtime 2 3 import ( 4 "math/bits" 5 "unsafe" 6 ) 7 8 // Number of bits in an uintptr. 9 const uintptrLen = 8 * unsafe.Sizeof(uintptr(0)) 10 11 // 12 // Implementation for Lua table. It is made of two parts, a hash table and an 13 // array, the latter containing only values with positive integer keys. 14 // 15 // The Value type needs to satisfy the interface { 16 // Hash() uintptr 17 // IsNil() bool 18 // Equals(Value) bool 19 // } 20 21 type mixedTable struct { 22 *hashTable 23 *array 24 } 25 26 // Return v such that k => v, else return nil. 27 func (t *mixedTable) get(k Value) Value { 28 i, ok := ToIntNoString(k) 29 if ok { 30 if v, ok := t.array.get(i); ok { 31 return v 32 } 33 k = IntValue(i) 34 } 35 return t.hashTable.find(k) 36 } 37 38 // Set k => v. 39 func (t *mixedTable) insert(k, v Value) { 40 i, ok := ToIntNoString(k) 41 if ok && t.array.setValue(i, v) { 42 return 43 } 44 if t.hashTable.full() { 45 t.grow() 46 if ok && t.array.setValue(i, v) { 47 return 48 } 49 } 50 if ok { 51 k = IntValue(i) 52 } 53 t.hashTable.set(k, v) 54 } 55 56 // Set k => v only if there is already v1 such that k => v1. Returns true if 57 // that is the case. 58 func (t *mixedTable) reset(k, v Value) (wasSet bool) { 59 i, ok := ToIntNoString(k) 60 if ok { 61 ok, wasSet = t.array.resetValue(i, v) 62 if ok { 63 return 64 } 65 } 66 if ok { 67 k = IntValue(i) 68 } 69 return t.hashTable.reset(k, v) 70 } 71 72 // Set k => nil, return true if there was v such that k => v. 73 func (t *mixedTable) remove(k Value) (wasSet bool) { 74 i, ok := ToIntNoString(k) 75 if ok { 76 if ok, wasSet = t.array.remove(i); ok { 77 return 78 } 79 k = IntValue(i) 80 } 81 82 return t.hashTable.removeKey(k) 83 } 84 85 // Return the "length" of the table, which is a positive integer such i => v but 86 // (i + 1) => nil. 87 func (t *mixedTable) len() uintptr { 88 l := t.array.getLen() 89 if l < t.array.size() { 90 return l 91 } 92 for !t.hashTable.find(IntValue(int64(l + 1))).IsNil() { 93 l++ 94 } 95 return l 96 } 97 98 // Return the next key-value after k in the table if it exists (in which case ok 99 // is true). If k is nil, return the first key-value in the table. 100 // 101 // Provided no new key is inserted between successive calls of next(), then the 102 // following code will iterate through all the key-value pairs in the table. 103 // 104 // var k Value 105 // for { 106 // k, v, ok = t.next(k) 107 // if !ok { 108 // break 109 // } 110 // } 111 func (t *mixedTable) next(k Value) (next Value, v Value, ok bool) { 112 var i int64 113 var isInt bool 114 if k.IsNil() { 115 if t.array == nil { 116 return t.hashTable.next(k) 117 } 118 // If there is an array, we pretend that k == 0 119 isInt = true 120 } else { 121 i, isInt = ToIntNoString(k) 122 } 123 if isInt { 124 j, v, ok := t.array.next(i) 125 if ok { 126 if j > 0 { 127 return IntValue(j), v, true 128 } 129 // In this case we have run out of values in the array, so start the 130 // hash table. 131 return t.hashTable.next(NilValue) 132 } 133 k = IntValue(i) 134 } 135 return t.hashTable.next(k) 136 } 137 138 // Grow the table - either the hash table part or the array part. 139 // 140 // The array part grows if there is a power of 2, N, bigger than the current 141 // size of the array and such that at least N/2 integer keys between 1 and N 142 // belong to the table, including one >= N/2. It then grows to size N and all 143 // the keys between 1 and N are transferred to it. 144 // 145 // If the array part doesn't grow, then the hash table part grows by a factor of 146 // 2. 147 // 148 // After growing the array it is guaranteed that there is at least one free slot 149 // in the hash table part. 150 func (t *mixedTable) grow() { 151 var idxCountByLen [uintptrLen]uintptr 152 153 // Classify the keys in the hashtable 154 idxCount := t.hashTable.classifyIndices(&idxCountByLen) 155 156 // If there are no possible index values, just grow the hash table 157 if idxCount == 0 { 158 t.hashTable = t.hashTable.grow() 159 return 160 } 161 162 // Find out if we should grow the array 163 t.array.classifyIndices(&idxCountByLen) 164 arrSize := calculateArraySize(&idxCountByLen) 165 166 // If the array shouldn't grow, grow the hash table 167 if arrSize <= t.array.size() { 168 t.hashTable = t.hashTable.grow() 169 return 170 } 171 172 // Grow the array. That should free capacity in the hashtable 173 array := t.array.grow(arrSize) 174 for i := range t.hashTable.slots { 175 it := &t.hashTable.slots[i] 176 if it.value.IsNil() { 177 continue 178 } 179 j, ok := it.key.TryInt() 180 if ok && array.setValue(j, it.value) { 181 it.value = NilValue 182 } 183 } 184 t.array = array 185 t.hashTable.cleanup() 186 } 187 188 // 189 // Hash table implementation 190 // 191 192 // A hashTable contains an array of slots, `S_1, ... S_N` which contain 193 // key-value pairs. Each slot can also point at (at most) another slot, which 194 // we represent as `S_i -> S_j`. 195 // 196 // By following the arrow we can form sequences of slots which we call "chains". 197 // 198 // There is a function that maps each key `k` to a given slot `S(k)` (in 199 // practice we use hash(k) % N). Let's call this slot the "primary slot" of 200 // `k`. 201 // 202 // As we populate the hash table, we keep the following invariants: 203 // 204 // (I1) All chains are finite (no cycles). 205 // 206 // (I2) All items in a chain have the same primary slot. 207 // 208 // (I3) The first item in a chain is in its primary slot. 209 // 210 // Having those invariants mean that it is easy to find a key `k` in the table: 211 // just start at its primary slot and follow the chain until you find a slot 212 // containing `k` or reach the end of the chain. 213 // 214 // To insert a new key-value pair k => v into the table, consider the primary 215 // slot `S` of `k`. There are 3 possibilities. 216 // 217 // (1) Slot `S` is free. This is simple: just put (k, v) in this slot. 218 // 219 // (2) Slot `S` already contains an item `J` for which it is the primary slot. 220 // Move it to the next free slot `F` and put the new key-value pair in `S`, so 221 // that the chain `S -> S'...` becomes `S -> F -> S'...` 222 // 223 // (3) Slot `S` contains an item `J` not in its primary position. Because of 224 // (I2) there is a chain `...S' -> S -> S''...`. We move `J` to the next free 225 // slot `F`, adjusting the chain as `...S' -> F -> S''...`. That frees slot 226 // `S`, which means we can put the new item in it. 227 // 228 // It is easy to check that in the 3 cases all invariants (I1), (I2) and (I3) 229 // are preserved. 230 type hashTable struct { 231 slots []hashTableSlot 232 nextFree uintptr 233 base uint8 234 } 235 236 type hashTableSlot struct { 237 key, value Value 238 next uintptr // Where to look next for colliding keys (and flags) 239 } 240 241 const ( 242 hasNextFlag uintptr = 1 // flags that another item is chained after this one 243 chainedFlag uintptr = 2 // flags that this item is chained (thus not in primary position) 244 nextFlags = hasNextFlag | chainedFlag 245 ) 246 247 const noNextFree uintptr = 1<<uintptrLen - 1 248 249 // Small hash tables are treated differently (we bypass hashing the keys). 250 const smallHashTableSize = 8 251 252 func (it hashTableSlot) hasNext() bool { 253 return it.next&hasNextFlag != 0 254 } 255 256 func (it hashTableSlot) nextIndex() uintptr { 257 return it.next >> 2 258 } 259 260 func (it hashTableSlot) isChained() bool { 261 return it.next&chainedFlag != 0 262 } 263 264 func (it hashTableSlot) isEmpty() bool { 265 return it.key.IsNil() 266 } 267 268 func (it *hashTableSlot) setNext(next uintptr, flags uintptr) { 269 it.next = next<<2 | flags 270 } 271 272 func (it *hashTableSlot) nextFlags() uintptr { 273 return it.next & nextFlags 274 } 275 276 func (t *hashTable) set(k, v Value) { 277 if setKeyValue(t.slots, (1<<t.base)-1, k, v, t.nextFree) { 278 t.nextFree = updateNextFree(t.slots, t.nextFree) 279 } 280 } 281 282 func (t *hashTable) reset(k, v Value) bool { 283 if t == nil { 284 return false 285 } 286 return resetKeyValue(t.slots, (1<<t.base)-1, k, v) 287 } 288 289 func (t *hashTable) find(k Value) Value { 290 if t == nil { 291 return NilValue 292 } 293 it, _ := findSlot(t.slots, (1<<t.base)-1, k) 294 if it == nil { 295 return NilValue 296 } 297 return it.value 298 } 299 300 func (t *hashTable) removeKey(k Value) (wasSet bool) { 301 if t == nil { 302 return false 303 } 304 return removeKey(t.slots, (1<<t.base)-1, k) 305 } 306 307 func (t *hashTable) full() bool { 308 return t == nil || t.nextFree == noNextFree 309 } 310 311 func (t *hashTable) grow() *hashTable { 312 if t == nil { 313 return &hashTable{ 314 slots: make([]hashTableSlot, 1), 315 } 316 } 317 var ( 318 base = t.base + 1 319 sz uintptr = 1 << base 320 mask uintptr = sz - 1 321 items = make([]hashTableSlot, sz) 322 ) 323 324 // Populate the new 325 t.nextFree = copyItems(items, t.slots, mask, mask) 326 t.base = base 327 t.slots = items 328 return t 329 } 330 331 func (t *hashTable) cleanup() { 332 if t == nil { 333 return 334 } 335 mask := uintptr(len(t.slots) - 1) 336 items := make([]hashTableSlot, len(t.slots)) 337 t.nextFree = copyItems(items, t.slots, mask, mask) 338 t.slots = items 339 } 340 341 func (t *hashTable) next(k Value) (next Value, v Value, ok bool) { 342 if t == nil { 343 return NilValue, NilValue, k.IsNil() 344 } 345 346 // Find the starting point 347 var i uintptr 348 if !k.IsNil() { 349 var it *hashTableSlot 350 it, i = findSlot(t.slots, (1<<t.base)-1, k) 351 if it == nil { 352 return 353 } 354 i++ 355 } 356 357 // Iterate to the next item 358 var nextIt hashTableSlot 359 for { 360 if int(i) >= len(t.slots) { 361 return NilValue, NilValue, true 362 } 363 nextIt = t.slots[i] 364 if !nextIt.value.IsNil() { 365 return nextIt.key, nextIt.value, true 366 } 367 i++ 368 } 369 } 370 371 func (t *hashTable) classifyIndices(idxCountByLen *[uintptrLen]uintptr) (idxCount uintptr) { 372 if t == nil { 373 return 374 } 375 for _, it := range t.slots { 376 if it.value.IsNil() { 377 continue 378 } 379 if i, ok := it.key.TryInt(); ok && i > 0 { 380 idxCountByLen[bits.Len(uint(i-1))]++ 381 idxCount++ 382 } 383 } 384 return 385 } 386 func copyItems(items, from []hashTableSlot, mask uintptr, nextFree uintptr) uintptr { 387 for _, it := range from { 388 if !it.value.IsNil() { 389 if insertNewKeyValue(items, mask, it.key, it.value, nextFree) { 390 nextFree = updateNextFree(items, nextFree) 391 } 392 } 393 } 394 return nextFree 395 } 396 397 func setKeyValue(items []hashTableSlot, mask uintptr, k, v Value, nextFree uintptr) bool { 398 if it, _ := findSlot(items, mask, k); it != nil { 399 it.value = v 400 return false 401 } 402 return insertNewKeyValue(items, mask, k, v, nextFree) 403 } 404 405 func resetKeyValue(items []hashTableSlot, mask uintptr, k, v Value) (wasSet bool) { 406 it, _ := findSlot(items, mask, k) 407 wasSet = it != nil && !it.value.IsNil() 408 if wasSet { 409 it.value = v 410 } 411 return 412 } 413 414 func insertNewKeyValue(items []hashTableSlot, mask uintptr, k, v Value, nextFree uintptr) bool { 415 it := hashTableSlot{key: k, value: v} 416 417 // Just fill a small table, it's faster than calculating hashes. 418 if mask < smallHashTableSize { 419 items[nextFree] = it 420 return true 421 } 422 var ( 423 i = k.Hash() & mask // primary position for the new item 424 cit = items[i] // item currently at primary position 425 ) 426 switch { 427 case cit.isEmpty(): 428 // The simple case. 429 items[i] = it 430 return i == nextFree 431 case cit.isChained(): 432 // Move new item into primary position, move colliding item into free position. 433 pidx := cit.key.Hash() & mask 434 pit := &items[pidx] 435 for nidx := pit.nextIndex(); nidx != i; nidx = pit.nextIndex() { 436 pidx = nidx 437 pit = &items[pidx] 438 } 439 items[nextFree] = cit 440 items[i] = it 441 pit.setNext(nextFree, pit.nextFlags()|hasNextFlag) 442 return true 443 default: 444 // Colliding item is in primary position, put new item into free position. 445 cit.next |= chainedFlag 446 items[nextFree] = cit 447 it.setNext(nextFree, hasNextFlag) 448 items[i] = it 449 return true 450 } 451 } 452 453 func updateNextFree(slots []hashTableSlot, nextFree uintptr) uintptr { 454 for nextFree != noNextFree && !slots[nextFree].isEmpty() { 455 nextFree-- 456 } 457 return nextFree 458 } 459 460 func findSlot(slots []hashTableSlot, mask uintptr, k Value) (it *hashTableSlot, i uintptr) { 461 // For a small table, it's cheaper not to calculate the hash 462 if mask < smallHashTableSize { 463 for j := int(mask); j >= 0; j-- { 464 it = &slots[j] 465 if it.key.Equals(k) { 466 i = uintptr(j) 467 return 468 } 469 } 470 return nil, 0 471 } 472 i = k.Hash() & mask 473 it = &slots[i] 474 if it.isChained() { 475 return nil, 0 476 } 477 for !it.key.Equals(k) { 478 if !it.hasNext() { 479 return nil, 0 480 } 481 i = it.nextIndex() 482 it = &slots[i] 483 } 484 return 485 } 486 487 func removeKey(slots []hashTableSlot, mask uintptr, k Value) (wasSet bool) { 488 if it, _ := findSlot(slots, mask, k); it != nil { 489 wasSet = !it.value.IsNil() 490 it.value = NilValue 491 } 492 return 493 } 494 495 // 496 // array implemetation 497 // 498 499 type array struct { 500 values []Value 501 len uintptr 502 } 503 504 func (a *array) get(i int64) (v Value, ok bool) { 505 ok = a != nil && 1 <= i && i <= int64(len(a.values)) 506 if ok { 507 v = a.values[i-1] 508 } 509 return 510 } 511 512 func (a *array) setValue(i int64, v Value) (ok bool) { 513 ok = a != nil && 1 <= i && i <= int64(len(a.values)) 514 if ok { 515 a.values[i-1] = v 516 if a.len < uintptr(i) { 517 a.len = uintptr(i) 518 } 519 } 520 return 521 } 522 523 func (a *array) resetValue(i int64, v Value) (ok bool, wasSet bool) { 524 ok = a != nil && 1 <= i && i <= int64(len(a.values)) 525 if ok { 526 wasSet = !a.values[i-1].IsNil() 527 if wasSet { 528 a.values[i-1] = v 529 } 530 } 531 return 532 } 533 534 func (a *array) remove(i int64) (ok bool, wasSet bool) { 535 ok = a != nil && 1 <= i && i <= int64(len(a.values)) 536 if !ok { 537 return 538 } 539 wasSet = int64(a.len) >= i && !a.values[i-1].IsNil() 540 if !wasSet { 541 return 542 } 543 a.values[i-1] = NilValue 544 l := uintptr(i) 545 if a.len == l { 546 for l >= 1 && a.values[l-1].IsNil() { 547 l-- 548 } 549 a.len = l 550 } 551 return 552 } 553 554 func (a *array) size() uintptr { 555 if a == nil { 556 return 0 557 } 558 return uintptr(len(a.values)) 559 } 560 561 func (a *array) getLen() uintptr { 562 if a == nil { 563 return 0 564 } 565 return a.len 566 } 567 568 func (a *array) next(i int64) (next int64, v Value, ok bool) { 569 ok = a != nil && 0 <= i && i <= int64(a.len) 570 if !ok { 571 return 572 } 573 for { 574 if i == int64(a.len) { 575 return 576 } 577 v = a.values[i] 578 i++ 579 if !v.IsNil() { 580 next = i 581 return 582 } 583 } 584 } 585 586 func (a *array) grow(sz uintptr) *array { 587 values := make([]Value, sz) 588 if a == nil { 589 return &array{values: values} 590 } 591 copy(values, a.values) 592 a.values = values 593 return a 594 } 595 596 func (a *array) classifyIndices(idxCountByLen *[uintptrLen]uintptr) { 597 if a == nil { 598 return 599 } 600 for i, v := range a.values[:a.len] { 601 if !v.IsNil() { 602 idxCountByLen[bits.Len(uint(i))]++ 603 } 604 } 605 } 606 607 func calculateArraySize(idxCountByLen *[uintptrLen]uintptr) uintptr { 608 var base = -1 609 var idxCount uintptr 610 for l, c := range idxCountByLen { 611 idxCount += c 612 if c != 0 && (l == 0 || idxCount >= 1<<(l-1)) { 613 base = l 614 } 615 } 616 if base >= 0 { 617 return 1 << base 618 } 619 return 0 620 }