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 ? $toObject(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 ? $toObject(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);