• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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