• 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 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