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.
16 (function(global, utils) {
19 %CheckIsBootstrapping();
21 // -------------------------------------------------------------------
24 var GlobalMap = global.Map;
25 var GlobalObject = global.Object;
26 var GlobalSet = global.Set;
29 utils.Import(function(from) {
30 IntRandom = from.IntRandom;
35 utils.Import(function(from) {
36 NumberIsNaN = from.NumberIsNaN;
39 // -------------------------------------------------------------------
41 function HashToEntry(table, hash, numBuckets) {
42 var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets);
43 return ORDERED_HASH_TABLE_BUCKET_AT(table, bucket);
45 %SetForceInlineFlag(HashToEntry);
48 function SetFindEntry(table, numBuckets, key, hash) {
49 var entry = HashToEntry(table, hash, numBuckets);
50 if (entry === NOT_FOUND) return entry;
51 var candidate = ORDERED_HASH_SET_KEY_AT(table, entry, numBuckets);
52 if (key === candidate) return entry;
53 var keyIsNaN = NumberIsNaN(key);
55 if (keyIsNaN && NumberIsNaN(candidate)) {
58 entry = ORDERED_HASH_SET_CHAIN_AT(table, entry, numBuckets);
59 if (entry === NOT_FOUND) return entry;
60 candidate = ORDERED_HASH_SET_KEY_AT(table, entry, numBuckets);
61 if (key === candidate) return entry;
65 %SetForceInlineFlag(SetFindEntry);
68 function MapFindEntry(table, numBuckets, key, hash) {
69 var entry = HashToEntry(table, hash, numBuckets);
70 if (entry === NOT_FOUND) return entry;
71 var candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets);
72 if (key === candidate) return entry;
73 var keyIsNaN = NumberIsNaN(key);
75 if (keyIsNaN && NumberIsNaN(candidate)) {
78 entry = ORDERED_HASH_MAP_CHAIN_AT(table, entry, numBuckets);
79 if (entry === NOT_FOUND) return entry;
80 candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets);
81 if (key === candidate) return entry;
85 %SetForceInlineFlag(MapFindEntry);
88 function ComputeIntegerHash(key, seed) {
91 hash = ~hash + (hash << 15); // hash = (hash << 15) - hash - 1;
92 hash = hash ^ (hash >>> 12);
93 hash = hash + (hash << 2);
94 hash = hash ^ (hash >>> 4);
95 hash = (hash * 2057) | 0; // hash = (hash + (hash << 3)) + (hash << 11);
96 hash = hash ^ (hash >>> 16);
97 return hash & 0x3fffffff;
99 %SetForceInlineFlag(ComputeIntegerHash);
101 var hashCodeSymbol = GLOBAL_PRIVATE("hash_code_symbol");
103 function GetExistingHash(key) {
105 return ComputeIntegerHash(key, 0);
107 if (IS_STRING(key)) {
108 var field = %_StringGetRawHashField(key);
109 if ((field & 1 /* Name::kHashNotComputedMask */) === 0) {
110 return field >>> 2 /* Name::kHashShift */;
112 } else if (IS_SPEC_OBJECT(key) && !%_IsJSProxy(key) && !IS_GLOBAL(key)) {
113 var hash = GET_PRIVATE(key, hashCodeSymbol);
116 return %GenericHash(key);
118 %SetForceInlineFlag(GetExistingHash);
121 function GetHash(key) {
122 var hash = GetExistingHash(key);
123 if (IS_UNDEFINED(hash)) {
124 hash = IntRandom() | 0;
125 if (hash === 0) hash = 1;
126 SET_PRIVATE(key, hashCodeSymbol, hash);
130 %SetForceInlineFlag(GetHash);
133 // -------------------------------------------------------------------
136 function SetConstructor(iterable) {
137 if (!%_IsConstructCall()) {
138 throw MakeTypeError(kConstructorNotFunction, "Set");
141 %_SetInitialize(this);
143 if (!IS_NULL_OR_UNDEFINED(iterable)) {
144 var adder = this.add;
145 if (!IS_SPEC_FUNCTION(adder)) {
146 throw MakeTypeError(kPropertyNotFunction, 'add', this);
149 for (var value of iterable) {
150 %_CallFunction(this, value, adder);
156 function SetAdd(key) {
158 throw MakeTypeError(kIncompatibleMethodReceiver, 'Set.prototype.add', this);
160 // Normalize -0 to +0 as required by the spec.
161 // Even though we use SameValueZero as the comparison for the keys we don't
162 // want to ever store -0 as the key since the key is directly exposed when
167 var table = %_JSCollectionGetTable(this);
168 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
169 var hash = GetHash(key);
170 if (SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND) return this;
172 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
173 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
174 var capacity = numBuckets << 1;
175 if ((nof + nod) >= capacity) {
176 // Need to grow, bail out to runtime.
178 // Re-load state from the grown backing store.
179 table = %_JSCollectionGetTable(this);
180 numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
181 nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
182 nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
184 var entry = nof + nod;
185 var index = ORDERED_HASH_SET_ENTRY_TO_INDEX(entry, numBuckets);
186 var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets);
187 var chainEntry = ORDERED_HASH_TABLE_BUCKET_AT(table, bucket);
188 ORDERED_HASH_TABLE_SET_BUCKET_AT(table, bucket, entry);
189 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof + 1);
190 FIXED_ARRAY_SET(table, index, key);
191 FIXED_ARRAY_SET_SMI(table, index + 1, chainEntry);
196 function SetHas(key) {
198 throw MakeTypeError(kIncompatibleMethodReceiver, 'Set.prototype.has', this);
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 return SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND;
208 function SetDelete(key) {
210 throw MakeTypeError(kIncompatibleMethodReceiver,
211 'Set.prototype.delete', this);
213 var table = %_JSCollectionGetTable(this);
214 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
215 var hash = GetExistingHash(key);
216 if (IS_UNDEFINED(hash)) return false;
217 var entry = SetFindEntry(table, numBuckets, key, hash);
218 if (entry === NOT_FOUND) return false;
220 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table) - 1;
221 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table) + 1;
222 var index = ORDERED_HASH_SET_ENTRY_TO_INDEX(entry, numBuckets);
223 FIXED_ARRAY_SET(table, index, %_TheHole());
224 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof);
225 ORDERED_HASH_TABLE_SET_DELETED_COUNT(table, nod);
226 if (nof < (numBuckets >>> 1)) %SetShrink(this);
231 function SetGetSize() {
233 throw MakeTypeError(kIncompatibleMethodReceiver,
234 'Set.prototype.size', this);
236 var table = %_JSCollectionGetTable(this);
237 return ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
241 function SetClearJS() {
243 throw MakeTypeError(kIncompatibleMethodReceiver,
244 'Set.prototype.clear', this);
250 function SetForEach(f, receiver) {
252 throw MakeTypeError(kIncompatibleMethodReceiver,
253 'Set.prototype.forEach', this);
256 if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
257 var needs_wrapper = false;
258 if (IS_NULL(receiver)) {
259 if (%IsSloppyModeFunction(f)) receiver = UNDEFINED;
260 } else if (!IS_UNDEFINED(receiver)) {
261 needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver);
264 var iterator = new SetIterator(this, ITERATOR_KIND_VALUES);
266 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
267 var value_array = [UNDEFINED];
268 while (%SetIteratorNext(iterator, value_array)) {
269 if (stepping) %DebugPrepareStepInIfStepping(f);
270 key = value_array[0];
271 var new_receiver = needs_wrapper ? TO_OBJECT(receiver) : receiver;
272 %_CallFunction(new_receiver, key, key, this, f);
276 // -------------------------------------------------------------------
278 %SetCode(GlobalSet, SetConstructor);
279 %FunctionSetLength(GlobalSet, 0);
280 %FunctionSetPrototype(GlobalSet, new GlobalObject());
281 %AddNamedProperty(GlobalSet.prototype, "constructor", GlobalSet, DONT_ENUM);
282 %AddNamedProperty(GlobalSet.prototype, symbolToStringTag, "Set",
283 DONT_ENUM | READ_ONLY);
285 %FunctionSetLength(SetForEach, 1);
287 // Set up the non-enumerable functions on the Set prototype object.
288 utils.InstallGetter(GlobalSet.prototype, "size", SetGetSize);
289 utils.InstallFunctions(GlobalSet.prototype, DONT_ENUM, [
294 "forEach", SetForEach
298 // -------------------------------------------------------------------
301 function MapConstructor(iterable) {
302 if (!%_IsConstructCall()) {
303 throw MakeTypeError(kConstructorNotFunction, "Map");
306 %_MapInitialize(this);
308 if (!IS_NULL_OR_UNDEFINED(iterable)) {
309 var adder = this.set;
310 if (!IS_SPEC_FUNCTION(adder)) {
311 throw MakeTypeError(kPropertyNotFunction, 'set', this);
314 for (var nextItem of iterable) {
315 if (!IS_SPEC_OBJECT(nextItem)) {
316 throw MakeTypeError(kIteratorValueNotAnObject, nextItem);
318 %_CallFunction(this, nextItem[0], nextItem[1], adder);
324 function MapGet(key) {
326 throw MakeTypeError(kIncompatibleMethodReceiver,
327 'Map.prototype.get', this);
329 var table = %_JSCollectionGetTable(this);
330 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
331 var hash = GetExistingHash(key);
332 if (IS_UNDEFINED(hash)) return UNDEFINED;
333 var entry = MapFindEntry(table, numBuckets, key, hash);
334 if (entry === NOT_FOUND) return UNDEFINED;
335 return ORDERED_HASH_MAP_VALUE_AT(table, entry, numBuckets);
339 function MapSet(key, value) {
341 throw MakeTypeError(kIncompatibleMethodReceiver,
342 'Map.prototype.set', this);
344 // Normalize -0 to +0 as required by the spec.
345 // Even though we use SameValueZero as the comparison for the keys we don't
346 // want to ever store -0 as the key since the key is directly exposed when
352 var table = %_JSCollectionGetTable(this);
353 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
354 var hash = GetHash(key);
355 var entry = MapFindEntry(table, numBuckets, key, hash);
356 if (entry !== NOT_FOUND) {
357 var existingIndex = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets);
358 FIXED_ARRAY_SET(table, existingIndex + 1, value);
362 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
363 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
364 var capacity = numBuckets << 1;
365 if ((nof + nod) >= capacity) {
366 // Need to grow, bail out to runtime.
368 // Re-load state from the grown backing store.
369 table = %_JSCollectionGetTable(this);
370 numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
371 nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
372 nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
375 var index = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets);
376 var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets);
377 var chainEntry = ORDERED_HASH_TABLE_BUCKET_AT(table, bucket);
378 ORDERED_HASH_TABLE_SET_BUCKET_AT(table, bucket, entry);
379 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof + 1);
380 FIXED_ARRAY_SET(table, index, key);
381 FIXED_ARRAY_SET(table, index + 1, value);
382 FIXED_ARRAY_SET(table, index + 2, chainEntry);
387 function MapHas(key) {
389 throw MakeTypeError(kIncompatibleMethodReceiver,
390 'Map.prototype.has', this);
392 var table = %_JSCollectionGetTable(this);
393 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
394 var hash = GetHash(key);
395 return MapFindEntry(table, numBuckets, key, hash) !== NOT_FOUND;
399 function MapDelete(key) {
401 throw MakeTypeError(kIncompatibleMethodReceiver,
402 'Map.prototype.delete', this);
404 var table = %_JSCollectionGetTable(this);
405 var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
406 var hash = GetHash(key);
407 var entry = MapFindEntry(table, numBuckets, key, hash);
408 if (entry === NOT_FOUND) return false;
410 var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table) - 1;
411 var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table) + 1;
412 var index = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets);
413 FIXED_ARRAY_SET(table, index, %_TheHole());
414 FIXED_ARRAY_SET(table, index + 1, %_TheHole());
415 ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof);
416 ORDERED_HASH_TABLE_SET_DELETED_COUNT(table, nod);
417 if (nof < (numBuckets >>> 1)) %MapShrink(this);
422 function MapGetSize() {
424 throw MakeTypeError(kIncompatibleMethodReceiver,
425 'Map.prototype.size', this);
427 var table = %_JSCollectionGetTable(this);
428 return ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
432 function MapClearJS() {
434 throw MakeTypeError(kIncompatibleMethodReceiver,
435 'Map.prototype.clear', this);
441 function MapForEach(f, receiver) {
443 throw MakeTypeError(kIncompatibleMethodReceiver,
444 'Map.prototype.forEach', this);
447 if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
448 var needs_wrapper = false;
449 if (IS_NULL(receiver)) {
450 if (%IsSloppyModeFunction(f)) receiver = UNDEFINED;
451 } else if (!IS_UNDEFINED(receiver)) {
452 needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver);
455 var iterator = new MapIterator(this, ITERATOR_KIND_ENTRIES);
456 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
457 var value_array = [UNDEFINED, UNDEFINED];
458 while (%MapIteratorNext(iterator, value_array)) {
459 if (stepping) %DebugPrepareStepInIfStepping(f);
460 var new_receiver = needs_wrapper ? TO_OBJECT(receiver) : receiver;
461 %_CallFunction(new_receiver, value_array[1], value_array[0], this, f);
465 // -------------------------------------------------------------------
467 %SetCode(GlobalMap, MapConstructor);
468 %FunctionSetLength(GlobalMap, 0);
469 %FunctionSetPrototype(GlobalMap, new GlobalObject());
470 %AddNamedProperty(GlobalMap.prototype, "constructor", GlobalMap, DONT_ENUM);
472 GlobalMap.prototype, symbolToStringTag, "Map", DONT_ENUM | READ_ONLY);
474 %FunctionSetLength(MapForEach, 1);
476 // Set up the non-enumerable functions on the Map prototype object.
477 utils.InstallGetter(GlobalMap.prototype, "size", MapGetSize);
478 utils.InstallFunctions(GlobalMap.prototype, DONT_ENUM, [
484 "forEach", MapForEach
487 // Expose to the global scope.
489 $getExistingHash = GetExistingHash;
493 $mapDelete = MapDelete;
496 $setDelete = SetDelete;
498 $mapFromArray = function(array) {
499 var map = new GlobalMap;
500 var length = array.length;
501 for (var i = 0; i < length; i += 2) {
503 var value = array[i + 1];
504 %_CallFunction(map, key, value, MapSet);
509 $setFromArray = function(array) {
510 var set = new GlobalSet;
511 var length = array.length;
512 for (var i = 0; i < length; ++i) {
513 %_CallFunction(set, array[i], SetAdd);