Home | History | Annotate | Download | only in js
      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 
     13 var GlobalMap = global.Map;
     14 var GlobalObject = global.Object;
     15 var GlobalSet = global.Set;
     16 var hashCodeSymbol = utils.ImportNow("hash_code_symbol");
     17 var MathRandom = global.Math.random;
     18 var MapIterator;
     19 var SetIterator;
     20 var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
     21 
     22 utils.Import(function(from) {
     23   MapIterator = from.MapIterator;
     24   SetIterator = from.SetIterator;
     25 });
     26 
     27 // -------------------------------------------------------------------
     28 
     29 function 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 
     36 function 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 
     56 function 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 
     76 function 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 
     89 function 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 
    107 function 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 
    122 function 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 
    142 function 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 
    182 function 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 
    194 function 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 
    217 function 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 
    227 function SetClearJS() {
    228   if (!IS_SET(this)) {
    229     throw %make_type_error(kIncompatibleMethodReceiver,
    230                         'Set.prototype.clear', this);
    231   }
    232   %_SetClear(this);
    233 }
    234 
    235 
    236 function 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.
    265 utils.InstallGetter(GlobalSet.prototype, "size", SetGetSize);
    266 utils.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 
    278 function 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 
    301 function 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 
    316 function 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 
    364 function 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 
    376 function 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 
    399 function 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 
    409 function MapClearJS() {
    410   if (!IS_MAP(this)) {
    411     throw %make_type_error(kIncompatibleMethodReceiver,
    412                         'Map.prototype.clear', this);
    413   }
    414   %_MapClear(this);
    415 }
    416 
    417 
    418 function 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.
    445 utils.InstallGetter(GlobalMap.prototype, "size", MapGetSize);
    446 utils.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 
    468 utils.Export(function(to) {
    469   to.GetExistingHash = GetExistingHash;
    470   to.GetHash = GetHash;
    471 });
    472 
    473 })
    474