github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/starext/dict.go (about)

     1  // https://github.com/google/starlark-go/pull/403
     2  
     3  package starext
     4  
     5  import (
     6  	"math"
     7  	"sort"
     8  	_ "unsafe" // for go:linkname hack
     9  
    10  	"go.starlark.net/starlark"
    11  )
    12  
    13  const (
    14  	bucketSize = 8
    15  )
    16  
    17  // hashString computes the hash of s.
    18  func hashString(s string) uint32 {
    19  	if len(s) >= 12 {
    20  		// Call the Go runtime's optimized hash implementation,
    21  		// which uses the AESENC instruction on amd64 machines.
    22  		return uint32(goStringHash(s, 0))
    23  	}
    24  	return softHashString(s)
    25  }
    26  
    27  //go:linkname goStringHash runtime.stringHash
    28  func goStringHash(s string, seed uintptr) uintptr
    29  
    30  // softHashString computes the 32-bit FNV-1a hash of s in software.
    31  func softHashString(s string) uint32 {
    32  	var h uint32 = 2166136261
    33  	for i := 0; i < len(s); i++ {
    34  		h ^= uint32(s[i])
    35  		h *= 16777619
    36  	}
    37  	return h
    38  }
    39  
    40  // An OrderedStringDict is a mutable mapping from names to values with
    41  // support for fast indexing. Keys are stored in order of unique insertion.
    42  // Keys are fast to add and access but slow to delete.
    43  // It is not a true starlark.Value.
    44  type OrderedStringDict struct {
    45  	// Hash table that maps names to indicies within entries.
    46  	table   []osdBucket        // len is zero or a power of two
    47  	bucket0 [bucketSize]uint32 // inline allocation for small maps
    48  	entries []osdEntry         // entries in order of insertion
    49  }
    50  
    51  // NewOrderedStringDict returns a dictionary with initial space for
    52  // at least size insertions before rehashing.
    53  func NewOrderedStringDict(size int) *OrderedStringDict {
    54  	dict := new(OrderedStringDict)
    55  	dict.init(size)
    56  	return dict
    57  }
    58  
    59  type osdBucket []uint32 // index to entries
    60  
    61  type osdEntry struct {
    62  	key   string
    63  	value starlark.Value
    64  	hash  uint32
    65  }
    66  
    67  func (d *OrderedStringDict) init(size int) {
    68  	if size < 0 {
    69  		panic("size < 0")
    70  	}
    71  	nb := 1
    72  	for overloaded(size, nb) {
    73  		nb = nb << 1
    74  	}
    75  	if nb < 2 {
    76  		d.table = []osdBucket{d.bucket0[:0]}
    77  	} else {
    78  		d.table = make([]osdBucket, nb)
    79  		for i := range d.table {
    80  			d.table[i] = make(osdBucket, 0, bucketSize)
    81  		}
    82  	}
    83  	d.entries = make([]osdEntry, 0, size)
    84  }
    85  
    86  func (d *OrderedStringDict) rehash() {
    87  	// TODO: shrink?
    88  	for i, l := 0, len(d.table); i < l; i++ {
    89  		d.table[i] = d.table[i][:0]
    90  	}
    91  	oldEntries := d.entries
    92  	d.entries = d.entries[:0]
    93  	for i, l := 0, len(oldEntries); i < l; i++ {
    94  		e := oldEntries[i]
    95  		d.insert(e.hash, e.key, e.value)
    96  	}
    97  }
    98  
    99  func (d *OrderedStringDict) grow() {
   100  	// Double the number of buckets and rehash.
   101  	newTable := make([]osdBucket, len(d.table)<<1)
   102  	for i, l := 0, len(d.table); i < l; i++ {
   103  		// Reuse bucket if below bucketSize.
   104  		p := d.table[i]
   105  		if cap(p) <= bucketSize {
   106  			newTable[i] = p[:0]
   107  		} else {
   108  			newTable[i] = make(osdBucket, 0, bucketSize)
   109  		}
   110  	}
   111  	for i, l := len(d.table), len(newTable); i < l; i++ {
   112  		newTable[i] = make(osdBucket, 0, bucketSize)
   113  	}
   114  	d.table = newTable
   115  	oldEntries := d.entries
   116  	d.entries = make([]osdEntry, 0, len(d.entries)<<1)
   117  	for i, l := 0, len(oldEntries); i < l; i++ {
   118  		e := oldEntries[i]
   119  		d.insert(e.hash, e.key, e.value)
   120  	}
   121  }
   122  
   123  func overloaded(elems, buckets int) bool {
   124  	const loadFactor = 6.5 // just a guess
   125  	return elems >= bucketSize && float64(elems) >= loadFactor*float64(buckets)
   126  }
   127  
   128  func (d *OrderedStringDict) insert(h uint32, k string, v starlark.Value) {
   129  	if d.table == nil {
   130  		d.init(1)
   131  	}
   132  
   133  	// Does the number of elements exceed the buckets' load factor?
   134  	for overloaded(len(d.entries), len(d.table)) {
   135  		d.grow()
   136  	}
   137  
   138  	n := h & (uint32(len(d.table) - 1))
   139  	p := d.table[n]
   140  	for i, l := 0, len(p); i < l; i++ {
   141  		e := &d.entries[p[i]]
   142  		if h == e.hash && k == e.key {
   143  			e.value = v
   144  			return
   145  		}
   146  	}
   147  
   148  	// Append value to entries, linking the bucket to the entries list.
   149  	d.entries = append(d.entries, osdEntry{
   150  		hash:  h,
   151  		key:   k,
   152  		value: v,
   153  	})
   154  	i := len(d.entries) - 1
   155  	if i > math.MaxUint32 {
   156  		panic("max entries")
   157  	}
   158  	d.table[n] = append(p, uint32(i))
   159  }
   160  
   161  func (d *OrderedStringDict) Insert(k string, v starlark.Value) {
   162  	h := hashString(k)
   163  	d.insert(h, k, v)
   164  }
   165  
   166  func (d *OrderedStringDict) Get(k string) (v starlark.Value, found bool) {
   167  	if d.table == nil {
   168  		return starlark.None, false // empty
   169  	}
   170  
   171  	if l := len(d.entries); l <= bucketSize {
   172  		for i := 0; i < l; i++ {
   173  			e := &d.entries[i]
   174  			if k == e.key {
   175  				return e.value, true // found
   176  			}
   177  		}
   178  		return starlark.None, false // not found
   179  	}
   180  
   181  	h := hashString(k)
   182  
   183  	// Inspect each entry in the bucket slice.
   184  	p := d.table[h&(uint32(len(d.table)-1))]
   185  	for i, l := 0, len(p); i < l; i++ {
   186  		e := &d.entries[p[i]]
   187  		if h == e.hash && k == e.key {
   188  			return e.value, true // found
   189  		}
   190  	}
   191  	return starlark.None, false // not found
   192  }
   193  
   194  func (d *OrderedStringDict) Set(k string, v starlark.Value) (found bool) {
   195  	if d.table == nil {
   196  		return false // empty
   197  	}
   198  
   199  	h := hashString(k)
   200  
   201  	// Inspect each entry in the bucket slice.
   202  	p := d.table[h&(uint32(len(d.table)-1))]
   203  	for i, l := 0, len(p); i < l; i++ {
   204  		e := &d.entries[p[i]]
   205  		if h == e.hash && k == e.key {
   206  			e.value = v
   207  			return true // found and set
   208  		}
   209  	}
   210  	return false // not found
   211  }
   212  
   213  func (d *OrderedStringDict) Delete(k string) (v starlark.Value, found bool) {
   214  	if d.table == nil {
   215  		return starlark.None, false // empty
   216  	}
   217  
   218  	h := hashString(k)
   219  	n := h & (uint32(len(d.table) - 1))
   220  	p := d.table[n]
   221  	for i, l := 0, len(p); i < l; i++ {
   222  		j := p[i]
   223  		e := &d.entries[j]
   224  		if e.hash == h && k == e.key {
   225  			v := e.value
   226  			e.value = nil // remove pointers
   227  
   228  			d.entries = append(d.entries[:j], d.entries[j+1:]...)
   229  			d.rehash()
   230  
   231  			return v, true // deleted
   232  		}
   233  	}
   234  	return starlark.None, false // not found
   235  }
   236  
   237  func (d *OrderedStringDict) Clear() { d.init(1) }
   238  
   239  func (d *OrderedStringDict) Keys() []string {
   240  	keys := make([]string, 0, len(d.entries))
   241  	for i, l := 0, len(d.entries); i < l; i++ {
   242  		e := &d.entries[i]
   243  		keys = append(keys, e.key)
   244  	}
   245  	return keys
   246  }
   247  
   248  func (d *OrderedStringDict) Index(i int) starlark.Value {
   249  	return d.entries[i].value
   250  }
   251  func (d *OrderedStringDict) Len() int {
   252  	return len(d.entries)
   253  }
   254  func (d *OrderedStringDict) KeyIndex(i int) (string, starlark.Value) {
   255  	e := &d.entries[i]
   256  	return e.key, e.value
   257  }
   258  
   259  type osdKeySorter []osdEntry
   260  
   261  func (d osdKeySorter) Len() int           { return len(d) }
   262  func (d osdKeySorter) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
   263  func (d osdKeySorter) Less(i, j int) bool { return d[i].key < d[j].key }
   264  
   265  // Sort the dict by name.
   266  func (d *OrderedStringDict) Sort() {
   267  	sort.Sort(osdKeySorter(d.entries))
   268  	d.rehash()
   269  }