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  }