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