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 }