| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:typed_data'; | 5 import 'dart:typed_data'; |
| 6 import 'dart:_internal' as internal; | 6 import 'dart:_internal' as internal; |
| 7 | 7 |
| 8 // Hash table with open addressing that separates the index from keys/values. | 8 // Hash table with open addressing that separates the index from keys/values. |
| 9 | 9 |
| 10 abstract class _HashFieldBase { | 10 abstract class _HashFieldBase { |
| 11 // Each occupied entry in _index is a fixed-size integer that encodes a pair: | 11 // Each occupied entry in _index is a fixed-size integer that encodes a pair: |
| 12 // [ hash pattern for key | index of entry in _data ] | 12 // [ hash pattern for key | index of entry in _data ] |
| 13 // The hash pattern is based on hashCode, but is guaranteed to be non-zero. | 13 // The hash pattern is based on hashCode, but is guaranteed to be non-zero. |
| 14 // The length of _index is always a power of two, and there is always at | 14 // The length of _index is always a power of two, and there is always at |
| 15 // least one unoccupied entry. | 15 // least one unoccupied entry. |
| 16 // NOTE: When maps are deserialized, their _index and _hashMask is regenerated |
| 17 // lazily by _regenerateIndex. |
| 18 // TODO(koda): Consider also using null _index for tiny, linear-search tables. |
| 16 Uint32List _index = new Uint32List(_HashBase._INITIAL_INDEX_SIZE); | 19 Uint32List _index = new Uint32List(_HashBase._INITIAL_INDEX_SIZE); |
| 17 | 20 |
| 18 // Cached in-place mask for the hash pattern component. On 32-bit, the top | 21 // Cached in-place mask for the hash pattern component. |
| 19 // bits are wasted to avoid Mint allocation. | 22 int _hashMask = _HashBase._indexSizeToHashMask(_HashBase._INITIAL_INDEX_SIZE); |
| 20 // TODO(koda): Reclaim the bits by making the compiler treat hash patterns | |
| 21 // as unsigned words. | |
| 22 int _hashMask = internal.is64Bit ? | |
| 23 (1 << (32 - _HashBase._INITIAL_INDEX_BITS)) - 1 : | |
| 24 (1 << (30 - _HashBase._INITIAL_INDEX_BITS)) - 1; | |
| 25 | 23 |
| 26 // Fixed-length list of keys (set) or key/value at even/odd indices (map). | 24 // Fixed-length list of keys (set) or key/value at even/odd indices (map). |
| 27 List _data = new List(_HashBase._INITIAL_INDEX_SIZE); | 25 List _data = new List(_HashBase._INITIAL_INDEX_SIZE); |
| 28 | 26 |
| 29 // Length of _data that is used (i.e., keys + values for a map). | 27 // Length of _data that is used (i.e., keys + values for a map). |
| 30 int _usedData = 0; | 28 int _usedData = 0; |
| 31 | 29 |
| 32 // Number of deleted keys. | 30 // Number of deleted keys. |
| 33 int _deletedKeys = 0; | 31 int _deletedKeys = 0; |
| 34 } | 32 } |
| (...skipping 25 matching lines...) Expand all Loading... |
| 60 // The length of _index is twice the number of entries in _data, and both | 58 // The length of _index is twice the number of entries in _data, and both |
| 61 // are doubled when _data is full. Thus, _index will have a max load factor | 59 // are doubled when _data is full. Thus, _index will have a max load factor |
| 62 // of 1/2, which enables one more bit to be used for the hash. | 60 // of 1/2, which enables one more bit to be used for the hash. |
| 63 // TODO(koda): Consider growing _data by factor sqrt(2), twice as often. | 61 // TODO(koda): Consider growing _data by factor sqrt(2), twice as often. |
| 64 static const int _INITIAL_INDEX_BITS = 3; | 62 static const int _INITIAL_INDEX_BITS = 3; |
| 65 static const int _INITIAL_INDEX_SIZE = 1 << (_INITIAL_INDEX_BITS + 1); | 63 static const int _INITIAL_INDEX_SIZE = 1 << (_INITIAL_INDEX_BITS + 1); |
| 66 | 64 |
| 67 // Unused and deleted entries are marked by 0 and 1, respectively. | 65 // Unused and deleted entries are marked by 0 and 1, respectively. |
| 68 static const int _UNUSED_PAIR = 0; | 66 static const int _UNUSED_PAIR = 0; |
| 69 static const int _DELETED_PAIR = 1; | 67 static const int _DELETED_PAIR = 1; |
| 70 | 68 |
| 69 // On 32-bit, the top bits are wasted to avoid Mint allocation. |
| 70 // TODO(koda): Reclaim the bits by making the compiler treat hash patterns |
| 71 // as unsigned words. |
| 72 static int _indexSizeToHashMask(int indexSize) { |
| 73 int indexBits = indexSize.bitLength - 2; |
| 74 return internal.is64Bit ? (1 << (32 - indexBits)) - 1 : |
| 75 (1 << (30 - indexBits)) - 1; |
| 76 } |
| 77 |
| 71 static int _hashPattern(int fullHash, int hashMask, int size) { | 78 static int _hashPattern(int fullHash, int hashMask, int size) { |
| 72 final int maskedHash = fullHash & hashMask; | 79 final int maskedHash = fullHash & hashMask; |
| 73 // TODO(koda): Consider keeping bit length and use left shift. | 80 // TODO(koda): Consider keeping bit length and use left shift. |
| 74 return (maskedHash == 0) ? (size >> 1) : maskedHash * (size >> 1); | 81 return (maskedHash == 0) ? (size >> 1) : maskedHash * (size >> 1); |
| 75 } | 82 } |
| 76 | 83 |
| 77 // Linear probing. | 84 // Linear probing. |
| 78 static int _firstProbe(int fullHash, int sizeMask) { | 85 static int _firstProbe(int fullHash, int sizeMask) { |
| 79 final int i = fullHash & sizeMask; | 86 final int i = fullHash & sizeMask; |
| 80 // Light, fast shuffle to mitigate bad hashCode (e.g., sequential). | 87 // Light, fast shuffle to mitigate bad hashCode (e.g., sequential). |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 125 // TODO(koda): Consider in-place compaction and more costly CME check. | 132 // TODO(koda): Consider in-place compaction and more costly CME check. |
| 126 _init(_index.length, _hashMask, _data, _usedData); | 133 _init(_index.length, _hashMask, _data, _usedData); |
| 127 } else { | 134 } else { |
| 128 // TODO(koda): Support 32->64 bit transition (and adjust _hashMask). | 135 // TODO(koda): Support 32->64 bit transition (and adjust _hashMask). |
| 129 _init(_index.length << 1, _hashMask >> 1, _data, _usedData); | 136 _init(_index.length << 1, _hashMask >> 1, _data, _usedData); |
| 130 } | 137 } |
| 131 } | 138 } |
| 132 | 139 |
| 133 void clear() { | 140 void clear() { |
| 134 if (!isEmpty) { | 141 if (!isEmpty) { |
| 135 _init(_index.length, _hashMask); | 142 // Use _data.length, since _index might be null. |
| 143 _init(_data.length, _hashMask); |
| 136 } | 144 } |
| 137 } | 145 } |
| 138 | 146 |
| 139 // Allocate new _index and _data, and optionally copy existing contents. | 147 // Allocate new _index and _data, and optionally copy existing contents. |
| 140 void _init(int size, int hashMask, [List oldData, int oldUsed]) { | 148 void _init(int size, int hashMask, [List oldData, int oldUsed]) { |
| 141 assert(size & (size - 1) == 0); | 149 assert(size & (size - 1) == 0); |
| 142 assert(_HashBase._UNUSED_PAIR == 0); | 150 assert(_HashBase._UNUSED_PAIR == 0); |
| 143 _index = new Uint32List(size); | 151 _index = new Uint32List(size); |
| 144 _hashMask = hashMask; | 152 _hashMask = hashMask; |
| 145 _data = new List(size); | 153 _data = new List(size); |
| 146 _usedData = 0; | 154 _usedData = 0; |
| 147 _deletedKeys = 0; | 155 _deletedKeys = 0; |
| 148 if (oldData != null) { | 156 if (oldData != null) { |
| 149 for (int i = 0; i < oldUsed; i += 2) { | 157 for (int i = 0; i < oldUsed; i += 2) { |
| 150 var key = oldData[i]; | 158 var key = oldData[i]; |
| 151 if (!_HashBase._isDeleted(oldData, key)) { | 159 if (!_HashBase._isDeleted(oldData, key)) { |
| 152 // TODO(koda): While there are enough hash bits, avoid hashCode calls. | 160 // TODO(koda): While there are enough hash bits, avoid hashCode calls. |
| 153 this[key] = oldData[i + 1]; | 161 this[key] = oldData[i + 1]; |
| 154 } | 162 } |
| 155 } | 163 } |
| 156 } | 164 } |
| 157 } | 165 } |
| 166 |
| 167 int _getIndexLength() { |
| 168 return (_index == null) ? _regenerateIndex() : _index.length; |
| 169 } |
| 170 |
| 171 int _regenerateIndex() { |
| 172 assert(_index == null); |
| 173 _index = new Uint32List(_data.length); |
| 174 assert(_hashMask == 0); |
| 175 _hashMask = _HashBase._indexSizeToHashMask(_index.length); |
| 176 final int tmpUsed = _usedData; |
| 177 _usedData = 0; |
| 178 for (int i = 0; i < tmpUsed; i += 2) { |
| 179 // TODO(koda): Avoid redundant equality tests and stores into _data. |
| 180 this[_data[i]] = _data[i + 1]; |
| 181 } |
| 182 return _index.length; |
| 183 } |
| 158 | 184 |
| 159 void _insert(K key, V value, int hashPattern, int i) { | 185 void _insert(K key, V value, int hashPattern, int i) { |
| 160 if (_usedData == _data.length) { | 186 if (_usedData == _data.length) { |
| 161 _rehash(); | 187 _rehash(); |
| 162 this[key] = value; | 188 this[key] = value; |
| 163 } else { | 189 } else { |
| 164 assert(1 <= hashPattern && hashPattern < (1 << 32)); | 190 assert(1 <= hashPattern && hashPattern < (1 << 32)); |
| 165 final int index = _usedData >> 1; | 191 final int index = _usedData >> 1; |
| 166 assert((index & hashPattern) == 0); | 192 assert((index & hashPattern) == 0); |
| 167 _index[i] = hashPattern | index; | 193 _index[i] = hashPattern | index; |
| (...skipping 24 matching lines...) Expand all Loading... |
| 192 } | 218 } |
| 193 } | 219 } |
| 194 } | 220 } |
| 195 i = _HashBase._nextProbe(i, sizeMask); | 221 i = _HashBase._nextProbe(i, sizeMask); |
| 196 pair = _index[i]; | 222 pair = _index[i]; |
| 197 } | 223 } |
| 198 return firstDeleted >= 0 ? -firstDeleted : -i; | 224 return firstDeleted >= 0 ? -firstDeleted : -i; |
| 199 } | 225 } |
| 200 | 226 |
| 201 void operator[]=(K key, V value) { | 227 void operator[]=(K key, V value) { |
| 202 final int size = _index.length; | 228 final int size = _getIndexLength(); |
| 203 final int sizeMask = size - 1; | 229 final int sizeMask = size - 1; |
| 204 final int fullHash = _hashCode(key); | 230 final int fullHash = _hashCode(key); |
| 205 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 231 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
| 206 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); | 232 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); |
| 207 if (d > 0) { | 233 if (d > 0) { |
| 208 _data[d] = value; | 234 _data[d] = value; |
| 209 } else { | 235 } else { |
| 210 final int i = -d; | 236 final int i = -d; |
| 211 _insert(key, value, hashPattern, i); | 237 _insert(key, value, hashPattern, i); |
| 212 } | 238 } |
| 213 } | 239 } |
| 214 | 240 |
| 215 V putIfAbsent(K key, V ifAbsent()) { | 241 V putIfAbsent(K key, V ifAbsent()) { |
| 216 final int size = _index.length; | 242 final int size = _getIndexLength(); |
| 217 final int sizeMask = size - 1; | 243 final int sizeMask = size - 1; |
| 218 final int maxEntries = size >> 1; | 244 final int maxEntries = size >> 1; |
| 219 final int fullHash = _hashCode(key); | 245 final int fullHash = _hashCode(key); |
| 220 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 246 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
| 221 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); | 247 final int d = _findValueOrInsertPoint(key, fullHash, hashPattern, size); |
| 222 if (d > 0) { | 248 if (d > 0) { |
| 223 return _data[d]; | 249 return _data[d]; |
| 224 } | 250 } |
| 225 // 'ifAbsent' is allowed to modify the map. | 251 // 'ifAbsent' is allowed to modify the map. |
| 226 List oldData = _data; | 252 List oldData = _data; |
| 227 int oldCheckSum = _checkSum; | 253 int oldCheckSum = _checkSum; |
| 228 V value = ifAbsent(); | 254 V value = ifAbsent(); |
| 229 if (_isModifiedSince(oldData, oldCheckSum)) { | 255 if (_isModifiedSince(oldData, oldCheckSum)) { |
| 230 this[key] = value; | 256 this[key] = value; |
| 231 } else { | 257 } else { |
| 232 final int i = -d; | 258 final int i = -d; |
| 233 _insert(key, value, hashPattern, i); | 259 _insert(key, value, hashPattern, i); |
| 234 } | 260 } |
| 235 return value; | 261 return value; |
| 236 } | 262 } |
| 237 | 263 |
| 238 V remove(Object key) { | 264 V remove(Object key) { |
| 239 final int size = _index.length; | 265 final int size = _getIndexLength(); |
| 240 final int sizeMask = size - 1; | 266 final int sizeMask = size - 1; |
| 241 final int maxEntries = size >> 1; | 267 final int maxEntries = size >> 1; |
| 242 final int fullHash = _hashCode(key); | 268 final int fullHash = _hashCode(key); |
| 243 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 269 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
| 244 int i = _HashBase._firstProbe(fullHash, sizeMask); | 270 int i = _HashBase._firstProbe(fullHash, sizeMask); |
| 245 int pair = _index[i]; | 271 int pair = _index[i]; |
| 246 while (pair != _HashBase._UNUSED_PAIR) { | 272 while (pair != _HashBase._UNUSED_PAIR) { |
| 247 if (pair != _HashBase._DELETED_PAIR) { | 273 if (pair != _HashBase._DELETED_PAIR) { |
| 248 final int entry = hashPattern ^ pair; | 274 final int entry = hashPattern ^ pair; |
| 249 if (entry < maxEntries) { | 275 if (entry < maxEntries) { |
| 250 final int d = entry << 1; | 276 final int d = entry << 1; |
| 251 if (_equals(key, _data[d])) { | 277 if (_equals(key, _data[d])) { |
| 252 _index[i] = _HashBase._DELETED_PAIR; | 278 _index[i] = _HashBase._DELETED_PAIR; |
| 253 _HashBase._setDeletedAt(_data, d); | 279 _HashBase._setDeletedAt(_data, d); |
| 254 V value = _data[d + 1]; | 280 V value = _data[d + 1]; |
| 255 _HashBase._setDeletedAt(_data, d + 1); | 281 _HashBase._setDeletedAt(_data, d + 1); |
| 256 ++_deletedKeys; | 282 ++_deletedKeys; |
| 257 return value; | 283 return value; |
| 258 } | 284 } |
| 259 } | 285 } |
| 260 } | 286 } |
| 261 i = _HashBase._nextProbe(i, sizeMask); | 287 i = _HashBase._nextProbe(i, sizeMask); |
| 262 pair = _index[i]; | 288 pair = _index[i]; |
| 263 } | 289 } |
| 264 return null; | 290 return null; |
| 265 } | 291 } |
| 266 | 292 |
| 267 // If key is absent, return _data (which is never a value). | 293 // If key is absent, return _data (which is never a value). |
| 268 Object _getValueOrData(Object key) { | 294 Object _getValueOrData(Object key) { |
| 269 final int size = _index.length; | 295 final int size = _getIndexLength(); |
| 270 final int sizeMask = size - 1; | 296 final int sizeMask = size - 1; |
| 271 final int maxEntries = size >> 1; | 297 final int maxEntries = size >> 1; |
| 272 final int fullHash = _hashCode(key); | 298 final int fullHash = _hashCode(key); |
| 273 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); | 299 final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size); |
| 274 int i = _HashBase._firstProbe(fullHash, sizeMask); | 300 int i = _HashBase._firstProbe(fullHash, sizeMask); |
| 275 int pair = _index[i]; | 301 int pair = _index[i]; |
| 276 while (pair != _HashBase._UNUSED_PAIR) { | 302 while (pair != _HashBase._UNUSED_PAIR) { |
| 277 if (pair != _HashBase._DELETED_PAIR) { | 303 if (pair != _HashBase._DELETED_PAIR) { |
| 278 final int entry = hashPattern ^ pair; | 304 final int entry = hashPattern ^ pair; |
| 279 if (entry < maxEntries) { | 305 if (entry < maxEntries) { |
| (...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 554 E lookup(Object o) => _validKey(o) ? super.lookup(o) : null; | 580 E lookup(Object o) => _validKey(o) ? super.lookup(o) : null; |
| 555 bool remove(Object o) => _validKey(o) ? super.remove(o) : false; | 581 bool remove(Object o) => _validKey(o) ? super.remove(o) : false; |
| 556 | 582 |
| 557 _CompactLinkedCustomHashSet(this._equality, this._hasher, validKey) | 583 _CompactLinkedCustomHashSet(this._equality, this._hasher, validKey) |
| 558 : _validKey = (validKey != null) ? validKey : new _TypeTest<E>().test; | 584 : _validKey = (validKey != null) ? validKey : new _TypeTest<E>().test; |
| 559 | 585 |
| 560 Set<E> toSet() => | 586 Set<E> toSet() => |
| 561 new _CompactLinkedCustomHashSet<E>(_equality, _hasher, _validKey) | 587 new _CompactLinkedCustomHashSet<E>(_equality, _hasher, _validKey) |
| 562 ..addAll(this); | 588 ..addAll(this); |
| 563 } | 589 } |
| OLD | NEW |