1// Copyright 2012 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5(function(global, utils) { 6"use strict"; 7 8%CheckIsBootstrapping(); 9 10// ------------------------------------------------------------------- 11// Imports 12 13var GlobalMap = global.Map; 14var GlobalObject = global.Object; 15var GlobalSet = global.Set; 16var hashCodeSymbol = utils.ImportNow("hash_code_symbol"); 17var MathRandom = global.Math.random; 18var MapIterator; 19var SetIterator; 20var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); 21 22utils.Import(function(from) { 23 MapIterator = from.MapIterator; 24 SetIterator = from.SetIterator; 25}); 26 27// ------------------------------------------------------------------- 28 29function HashToEntry(table, hash, numBuckets) { 30 var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets); 31 return ORDERED_HASH_TABLE_BUCKET_AT(table, bucket); 32} 33%SetForceInlineFlag(HashToEntry); 34 35 36function SetFindEntry(table, numBuckets, key, hash) { 37 var entry = HashToEntry(table, hash, numBuckets); 38 if (entry === NOT_FOUND) return entry; 39 var candidate = ORDERED_HASH_SET_KEY_AT(table, entry, numBuckets); 40 if (key === candidate) return entry; 41 var keyIsNaN = NUMBER_IS_NAN(key); 42 while (true) { 43 if (keyIsNaN && NUMBER_IS_NAN(candidate)) { 44 return entry; 45 } 46 entry = ORDERED_HASH_SET_CHAIN_AT(table, entry, numBuckets); 47 if (entry === NOT_FOUND) return entry; 48 candidate = ORDERED_HASH_SET_KEY_AT(table, entry, numBuckets); 49 if (key === candidate) return entry; 50 } 51 return NOT_FOUND; 52} 53%SetForceInlineFlag(SetFindEntry); 54 55 56function MapFindEntry(table, numBuckets, key, hash) { 57 var entry = HashToEntry(table, hash, numBuckets); 58 if (entry === NOT_FOUND) return entry; 59 var candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets); 60 if (key === candidate) return entry; 61 var keyIsNaN = NUMBER_IS_NAN(key); 62 while (true) { 63 if (keyIsNaN && NUMBER_IS_NAN(candidate)) { 64 return entry; 65 } 66 entry = ORDERED_HASH_MAP_CHAIN_AT(table, entry, numBuckets); 67 if (entry === NOT_FOUND) return entry; 68 candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets); 69 if (key === candidate) return entry; 70 } 71 return NOT_FOUND; 72} 73%SetForceInlineFlag(MapFindEntry); 74 75 76function ComputeIntegerHash(key, seed) { 77 var hash = key; 78 hash = hash ^ seed; 79 hash = ~hash + (hash << 15); // hash = (hash << 15) - hash - 1; 80 hash = hash ^ (hash >>> 12); 81 hash = hash + (hash << 2); 82 hash = hash ^ (hash >>> 4); 83 hash = (hash * 2057) | 0; // hash = (hash + (hash << 3)) + (hash << 11); 84 hash = hash ^ (hash >>> 16); 85 return hash & 0x3fffffff; 86} 87%SetForceInlineFlag(ComputeIntegerHash); 88 89function GetExistingHash(key) { 90 if (%_IsSmi(key)) { 91 return ComputeIntegerHash(key, 0); 92 } 93 if (IS_STRING(key)) { 94 var field = %_StringGetRawHashField(key); 95 if ((field & 1 /* Name::kHashNotComputedMask */) === 0) { 96 return field >>> 2 /* Name::kHashShift */; 97 } 98 } else if (IS_RECEIVER(key) && !IS_PROXY(key) && !IS_GLOBAL(key)) { 99 var hash = GET_PRIVATE(key, hashCodeSymbol); 100 return hash; 101 } 102 return %GenericHash(key); 103} 104%SetForceInlineFlag(GetExistingHash); 105 106 107function GetHash(key) { 108 var hash = GetExistingHash(key); 109 if (IS_UNDEFINED(hash)) { 110 hash = (MathRandom() * 0x40000000) | 0; 111 if (hash === 0) hash = 1; 112 SET_PRIVATE(key, hashCodeSymbol, hash); 113 } 114 return hash; 115} 116%SetForceInlineFlag(GetHash); 117 118 119// ------------------------------------------------------------------- 120// Harmony Set 121 122function SetConstructor(iterable) { 123 if (IS_UNDEFINED(new.target)) { 124 throw %make_type_error(kConstructorNotFunction, "Set"); 125 } 126 127 %_SetInitialize(this); 128 129 if (!IS_NULL_OR_UNDEFINED(iterable)) { 130 var adder = this.add; 131 if (!IS_CALLABLE(adder)) { 132 throw %make_type_error(kPropertyNotFunction, adder, 'add', this); 133 } 134 135 for (var value of iterable) { 136 %_Call(adder, this, value); 137 } 138 } 139} 140 141 142function SetAdd(key) { 143 if (!IS_SET(this)) { 144 throw %make_type_error(kIncompatibleMethodReceiver, 'Set.prototype.add', this); 145 } 146 // Normalize -0 to +0 as required by the spec. 147 // Even though we use SameValueZero as the comparison for the keys we don't 148 // want to ever store -0 as the key since the key is directly exposed when 149 // doing iteration. 150 if (key === 0) { 151 key = 0; 152 } 153 var table = %_JSCollectionGetTable(this); 154 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 155 var hash = GetHash(key); 156 if (SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND) return this; 157 158 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table); 159 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table); 160 var capacity = numBuckets << 1; 161 if ((nof + nod) >= capacity) { 162 // Need to grow, bail out to runtime. 163 %SetGrow(this); 164 // Re-load state from the grown backing store. 165 table = %_JSCollectionGetTable(this); 166 numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 167 nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table); 168 nod = ORDERED_HASH_TABLE_DELETED_COUNT(table); 169 } 170 var entry = nof + nod; 171 var index = ORDERED_HASH_SET_ENTRY_TO_INDEX(entry, numBuckets); 172 var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets); 173 var chainEntry = ORDERED_HASH_TABLE_BUCKET_AT(table, bucket); 174 ORDERED_HASH_TABLE_SET_BUCKET_AT(table, bucket, entry); 175 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof + 1); 176 FIXED_ARRAY_SET(table, index, key); 177 FIXED_ARRAY_SET_SMI(table, index + 1, chainEntry); 178 return this; 179} 180 181 182function SetHas(key) { 183 if (!IS_SET(this)) { 184 throw %make_type_error(kIncompatibleMethodReceiver, 'Set.prototype.has', this); 185 } 186 var table = %_JSCollectionGetTable(this); 187 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 188 var hash = GetExistingHash(key); 189 if (IS_UNDEFINED(hash)) return false; 190 return SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND; 191} 192 193 194function SetDelete(key) { 195 if (!IS_SET(this)) { 196 throw %make_type_error(kIncompatibleMethodReceiver, 197 'Set.prototype.delete', this); 198 } 199 var table = %_JSCollectionGetTable(this); 200 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 201 var hash = GetExistingHash(key); 202 if (IS_UNDEFINED(hash)) return false; 203 var entry = SetFindEntry(table, numBuckets, key, hash); 204 if (entry === NOT_FOUND) return false; 205 206 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table) - 1; 207 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table) + 1; 208 var index = ORDERED_HASH_SET_ENTRY_TO_INDEX(entry, numBuckets); 209 FIXED_ARRAY_SET(table, index, %_TheHole()); 210 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof); 211 ORDERED_HASH_TABLE_SET_DELETED_COUNT(table, nod); 212 if (nof < (numBuckets >>> 1)) %SetShrink(this); 213 return true; 214} 215 216 217function SetGetSize() { 218 if (!IS_SET(this)) { 219 throw %make_type_error(kIncompatibleMethodReceiver, 220 'Set.prototype.size', this); 221 } 222 var table = %_JSCollectionGetTable(this); 223 return ORDERED_HASH_TABLE_ELEMENT_COUNT(table); 224} 225 226 227function SetClearJS() { 228 if (!IS_SET(this)) { 229 throw %make_type_error(kIncompatibleMethodReceiver, 230 'Set.prototype.clear', this); 231 } 232 %_SetClear(this); 233} 234 235 236function SetForEach(f, receiver) { 237 if (!IS_SET(this)) { 238 throw %make_type_error(kIncompatibleMethodReceiver, 239 'Set.prototype.forEach', this); 240 } 241 242 if (!IS_CALLABLE(f)) throw %make_type_error(kCalledNonCallable, f); 243 244 var iterator = new SetIterator(this, ITERATOR_KIND_VALUES); 245 var key; 246 var value_array = [UNDEFINED]; 247 while (%SetIteratorNext(iterator, value_array)) { 248 key = value_array[0]; 249 %_Call(f, receiver, key, key, this); 250 } 251} 252 253// ------------------------------------------------------------------- 254 255%SetCode(GlobalSet, SetConstructor); 256%FunctionSetLength(GlobalSet, 0); 257%FunctionSetPrototype(GlobalSet, new GlobalObject()); 258%AddNamedProperty(GlobalSet.prototype, "constructor", GlobalSet, DONT_ENUM); 259%AddNamedProperty(GlobalSet.prototype, toStringTagSymbol, "Set", 260 DONT_ENUM | READ_ONLY); 261 262%FunctionSetLength(SetForEach, 1); 263 264// Set up the non-enumerable functions on the Set prototype object. 265utils.InstallGetter(GlobalSet.prototype, "size", SetGetSize); 266utils.InstallFunctions(GlobalSet.prototype, DONT_ENUM, [ 267 "add", SetAdd, 268 "has", SetHas, 269 "delete", SetDelete, 270 "clear", SetClearJS, 271 "forEach", SetForEach 272]); 273 274 275// ------------------------------------------------------------------- 276// Harmony Map 277 278function MapConstructor(iterable) { 279 if (IS_UNDEFINED(new.target)) { 280 throw %make_type_error(kConstructorNotFunction, "Map"); 281 } 282 283 %_MapInitialize(this); 284 285 if (!IS_NULL_OR_UNDEFINED(iterable)) { 286 var adder = this.set; 287 if (!IS_CALLABLE(adder)) { 288 throw %make_type_error(kPropertyNotFunction, adder, 'set', this); 289 } 290 291 for (var nextItem of iterable) { 292 if (!IS_RECEIVER(nextItem)) { 293 throw %make_type_error(kIteratorValueNotAnObject, nextItem); 294 } 295 %_Call(adder, this, nextItem[0], nextItem[1]); 296 } 297 } 298} 299 300 301function MapGet(key) { 302 if (!IS_MAP(this)) { 303 throw %make_type_error(kIncompatibleMethodReceiver, 304 'Map.prototype.get', this); 305 } 306 var table = %_JSCollectionGetTable(this); 307 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 308 var hash = GetExistingHash(key); 309 if (IS_UNDEFINED(hash)) return UNDEFINED; 310 var entry = MapFindEntry(table, numBuckets, key, hash); 311 if (entry === NOT_FOUND) return UNDEFINED; 312 return ORDERED_HASH_MAP_VALUE_AT(table, entry, numBuckets); 313} 314 315 316function MapSet(key, value) { 317 if (!IS_MAP(this)) { 318 throw %make_type_error(kIncompatibleMethodReceiver, 319 'Map.prototype.set', this); 320 } 321 // Normalize -0 to +0 as required by the spec. 322 // Even though we use SameValueZero as the comparison for the keys we don't 323 // want to ever store -0 as the key since the key is directly exposed when 324 // doing iteration. 325 if (key === 0) { 326 key = 0; 327 } 328 329 var table = %_JSCollectionGetTable(this); 330 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 331 var hash = GetHash(key); 332 var entry = MapFindEntry(table, numBuckets, key, hash); 333 if (entry !== NOT_FOUND) { 334 var existingIndex = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets); 335 FIXED_ARRAY_SET(table, existingIndex + 1, value); 336 return this; 337 } 338 339 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table); 340 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table); 341 var capacity = numBuckets << 1; 342 if ((nof + nod) >= capacity) { 343 // Need to grow, bail out to runtime. 344 %MapGrow(this); 345 // Re-load state from the grown backing store. 346 table = %_JSCollectionGetTable(this); 347 numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 348 nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table); 349 nod = ORDERED_HASH_TABLE_DELETED_COUNT(table); 350 } 351 entry = nof + nod; 352 var index = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets); 353 var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets); 354 var chainEntry = ORDERED_HASH_TABLE_BUCKET_AT(table, bucket); 355 ORDERED_HASH_TABLE_SET_BUCKET_AT(table, bucket, entry); 356 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof + 1); 357 FIXED_ARRAY_SET(table, index, key); 358 FIXED_ARRAY_SET(table, index + 1, value); 359 FIXED_ARRAY_SET(table, index + 2, chainEntry); 360 return this; 361} 362 363 364function MapHas(key) { 365 if (!IS_MAP(this)) { 366 throw %make_type_error(kIncompatibleMethodReceiver, 367 'Map.prototype.has', this); 368 } 369 var table = %_JSCollectionGetTable(this); 370 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 371 var hash = GetHash(key); 372 return MapFindEntry(table, numBuckets, key, hash) !== NOT_FOUND; 373} 374 375 376function MapDelete(key) { 377 if (!IS_MAP(this)) { 378 throw %make_type_error(kIncompatibleMethodReceiver, 379 'Map.prototype.delete', this); 380 } 381 var table = %_JSCollectionGetTable(this); 382 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table); 383 var hash = GetHash(key); 384 var entry = MapFindEntry(table, numBuckets, key, hash); 385 if (entry === NOT_FOUND) return false; 386 387 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table) - 1; 388 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table) + 1; 389 var index = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets); 390 FIXED_ARRAY_SET(table, index, %_TheHole()); 391 FIXED_ARRAY_SET(table, index + 1, %_TheHole()); 392 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof); 393 ORDERED_HASH_TABLE_SET_DELETED_COUNT(table, nod); 394 if (nof < (numBuckets >>> 1)) %MapShrink(this); 395 return true; 396} 397 398 399function MapGetSize() { 400 if (!IS_MAP(this)) { 401 throw %make_type_error(kIncompatibleMethodReceiver, 402 'Map.prototype.size', this); 403 } 404 var table = %_JSCollectionGetTable(this); 405 return ORDERED_HASH_TABLE_ELEMENT_COUNT(table); 406} 407 408 409function MapClearJS() { 410 if (!IS_MAP(this)) { 411 throw %make_type_error(kIncompatibleMethodReceiver, 412 'Map.prototype.clear', this); 413 } 414 %_MapClear(this); 415} 416 417 418function MapForEach(f, receiver) { 419 if (!IS_MAP(this)) { 420 throw %make_type_error(kIncompatibleMethodReceiver, 421 'Map.prototype.forEach', this); 422 } 423 424 if (!IS_CALLABLE(f)) throw %make_type_error(kCalledNonCallable, f); 425 426 var iterator = new MapIterator(this, ITERATOR_KIND_ENTRIES); 427 var value_array = [UNDEFINED, UNDEFINED]; 428 while (%MapIteratorNext(iterator, value_array)) { 429 %_Call(f, receiver, value_array[1], value_array[0], this); 430 } 431} 432 433// ------------------------------------------------------------------- 434 435%SetCode(GlobalMap, MapConstructor); 436%FunctionSetLength(GlobalMap, 0); 437%FunctionSetPrototype(GlobalMap, new GlobalObject()); 438%AddNamedProperty(GlobalMap.prototype, "constructor", GlobalMap, DONT_ENUM); 439%AddNamedProperty( 440 GlobalMap.prototype, toStringTagSymbol, "Map", DONT_ENUM | READ_ONLY); 441 442%FunctionSetLength(MapForEach, 1); 443 444// Set up the non-enumerable functions on the Map prototype object. 445utils.InstallGetter(GlobalMap.prototype, "size", MapGetSize); 446utils.InstallFunctions(GlobalMap.prototype, DONT_ENUM, [ 447 "get", MapGet, 448 "set", MapSet, 449 "has", MapHas, 450 "delete", MapDelete, 451 "clear", MapClearJS, 452 "forEach", MapForEach 453]); 454 455// ----------------------------------------------------------------------- 456// Exports 457 458%InstallToContext([ 459 "map_get", MapGet, 460 "map_set", MapSet, 461 "map_has", MapHas, 462 "map_delete", MapDelete, 463 "set_add", SetAdd, 464 "set_has", SetHas, 465 "set_delete", SetDelete, 466]); 467 468utils.Export(function(to) { 469 to.GetExistingHash = GetExistingHash; 470 to.GetHash = GetHash; 471}); 472 473}) 474