Skip to content

Commit 909500a

Browse files
ajkleinCommit bot
authored andcommitted
Reimplement Maps and Sets in JS
Previously, the only optimized code path for Maps and Sets was for String keys. This was achieved through an implementation of various complex operations in Hydrogen. This approach was neither scalable nor forward-compatible. This patch adds the necessary intrinsics to implement Maps and Sets almost entirely in JS. The added intrinsics are: %_FixedArrayGet %_FixedArraySet %_TheHole %_JSCollectionGetTable %_StringGetRawHashField With these additions, as well as a few changes to what's exposed as runtime functions, most of the C++ code backing Maps and Sets is gone (including both runtime code in objects.cc and Crankshaft in hydrogen.cc). Review URL: https://codereview.chromium.org/947683002 Cr-Commit-Position: refs/heads/master@{#27605}
1 parent 31bbcc3 commit 909500a

18 files changed

Lines changed: 351 additions & 1062 deletions

src/arm/code-stubs-arm.cc

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,16 +1005,6 @@ void CEntryStub::Generate(MacroAssembler* masm) {
10051005

10061006
__ VFPEnsureFPSCRState(r2);
10071007

1008-
// Runtime functions should not return 'the hole'. Allowing it to escape may
1009-
// lead to crashes in the IC code later.
1010-
if (FLAG_debug_code) {
1011-
Label okay;
1012-
__ CompareRoot(r0, Heap::kTheHoleValueRootIndex);
1013-
__ b(ne, &okay);
1014-
__ stop("The hole escaped");
1015-
__ bind(&okay);
1016-
}
1017-
10181008
// Check result for exception sentinel.
10191009
Label exception_returned;
10201010
__ CompareRoot(r0, Heap::kExceptionRootIndex);

src/collection.js

Lines changed: 209 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,86 @@ var $Set = global.Set;
1212
var $Map = global.Map;
1313

1414

15+
// Used by harmony-templates.js
16+
var $MapGet;
17+
var $MapSet;
18+
19+
20+
(function() {
21+
22+
23+
function HashToEntry(table, hash, numBuckets) {
24+
var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets);
25+
return ORDERED_HASH_TABLE_BUCKET_AT(table, bucket);
26+
}
27+
%SetInlineBuiltinFlag(HashToEntry);
28+
29+
30+
function SetFindEntry(table, numBuckets, key, hash) {
31+
var keyIsNaN = IS_NUMBER(key) && NUMBER_IS_NAN(key);
32+
for (var entry = HashToEntry(table, hash, numBuckets);
33+
entry !== NOT_FOUND;
34+
entry = ORDERED_HASH_SET_CHAIN_AT(table, entry, numBuckets)) {
35+
var candidate = ORDERED_HASH_SET_KEY_AT(table, entry, numBuckets);
36+
if (key === candidate) {
37+
return entry;
38+
}
39+
if (keyIsNaN && IS_NUMBER(candidate) && NUMBER_IS_NAN(candidate)) {
40+
return entry;
41+
}
42+
}
43+
return NOT_FOUND;
44+
}
45+
%SetInlineBuiltinFlag(SetFindEntry);
46+
47+
48+
function MapFindEntry(table, numBuckets, key, hash) {
49+
var keyIsNaN = IS_NUMBER(key) && NUMBER_IS_NAN(key);
50+
for (var entry = HashToEntry(table, hash, numBuckets);
51+
entry !== NOT_FOUND;
52+
entry = ORDERED_HASH_MAP_CHAIN_AT(table, entry, numBuckets)) {
53+
var candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets);
54+
if (key === candidate) {
55+
return entry;
56+
}
57+
if (keyIsNaN && IS_NUMBER(candidate) && NUMBER_IS_NAN(candidate)) {
58+
return entry;
59+
}
60+
}
61+
return NOT_FOUND;
62+
}
63+
%SetInlineBuiltinFlag(MapFindEntry);
64+
65+
66+
function ComputeIntegerHash(key, seed) {
67+
var hash = key;
68+
hash = hash ^ seed;
69+
hash = ~hash + (hash << 15); // hash = (hash << 15) - hash - 1;
70+
hash = hash ^ (hash >>> 12);
71+
hash = hash + (hash << 2);
72+
hash = hash ^ (hash >>> 4);
73+
hash = (hash * 2057) | 0; // hash = (hash + (hash << 3)) + (hash << 11);
74+
hash = hash ^ (hash >>> 16);
75+
return hash;
76+
}
77+
%SetInlineBuiltinFlag(ComputeIntegerHash);
78+
79+
80+
function GetHash(key) {
81+
if (%_IsSmi(key)) {
82+
return ComputeIntegerHash(key, 0);
83+
}
84+
if (IS_STRING(key)) {
85+
var field = %_StringGetRawHashField(key);
86+
if ((field & 1 /* Name::kHashNotComputedMask */) === 0) {
87+
return field >>> 2 /* Name::kHashShift */;
88+
}
89+
}
90+
return %GenericHash(key);
91+
}
92+
%SetInlineBuiltinFlag(GetHash);
93+
94+
1595
// -------------------------------------------------------------------
1696
// Harmony Set
1797

@@ -35,7 +115,7 @@ function SetConstructor(iterable) {
35115
}
36116

37117

38-
function SetAddJS(key) {
118+
function SetAdd(key) {
39119
if (!IS_SET(this)) {
40120
throw MakeTypeError('incompatible_method_receiver',
41121
['Set.prototype.add', this]);
@@ -47,34 +127,76 @@ function SetAddJS(key) {
47127
if (key === 0) {
48128
key = 0;
49129
}
50-
return %_SetAdd(this, key);
130+
var table = %_JSCollectionGetTable(this);
131+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
132+
var hash = GetHash(key);
133+
if (SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND) return this;
134+
135+
var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
136+
var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
137+
var capacity = numBuckets << 1;
138+
if ((nof + nod) >= capacity) {
139+
// Need to grow, bail out to runtime.
140+
%SetGrow(this);
141+
// Re-load state from the grown backing store.
142+
table = %_JSCollectionGetTable(this);
143+
numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
144+
nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
145+
nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
146+
}
147+
var entry = nof + nod;
148+
var index = ORDERED_HASH_SET_ENTRY_TO_INDEX(entry, numBuckets);
149+
var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets);
150+
var chainEntry = ORDERED_HASH_TABLE_BUCKET_AT(table, bucket);
151+
ORDERED_HASH_TABLE_SET_BUCKET_AT(table, bucket, entry);
152+
ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof + 1);
153+
FIXED_ARRAY_SET(table, index, key);
154+
FIXED_ARRAY_SET_SMI(table, index + 1, chainEntry);
155+
return this;
51156
}
52157

53158

54-
function SetHasJS(key) {
159+
function SetHas(key) {
55160
if (!IS_SET(this)) {
56161
throw MakeTypeError('incompatible_method_receiver',
57162
['Set.prototype.has', this]);
58163
}
59-
return %_SetHas(this, key);
164+
var table = %_JSCollectionGetTable(this);
165+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
166+
var hash = GetHash(key);
167+
return SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND;
60168
}
61169

62170

63-
function SetDeleteJS(key) {
171+
function SetDelete(key) {
64172
if (!IS_SET(this)) {
65173
throw MakeTypeError('incompatible_method_receiver',
66174
['Set.prototype.delete', this]);
67175
}
68-
return %_SetDelete(this, key);
176+
var table = %_JSCollectionGetTable(this);
177+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
178+
var hash = GetHash(key);
179+
var entry = SetFindEntry(table, numBuckets, key, hash);
180+
if (entry === NOT_FOUND) return false;
181+
182+
var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table) - 1;
183+
var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table) + 1;
184+
var index = ORDERED_HASH_SET_ENTRY_TO_INDEX(entry, numBuckets);
185+
FIXED_ARRAY_SET(table, index, %_TheHole());
186+
ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof);
187+
ORDERED_HASH_TABLE_SET_DELETED_COUNT(table, nod);
188+
if (nof < (numBuckets >>> 1)) %SetShrink(this);
189+
return true;
69190
}
70191

71192

72-
function SetGetSizeJS() {
193+
function SetGetSize() {
73194
if (!IS_SET(this)) {
74195
throw MakeTypeError('incompatible_method_receiver',
75196
['Set.prototype.size', this]);
76197
}
77-
return %_SetGetSize(this);
198+
var table = %_JSCollectionGetTable(this);
199+
return ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
78200
}
79201

80202

@@ -130,11 +252,11 @@ function SetUpSet() {
130252
%FunctionSetLength(SetForEach, 1);
131253

132254
// Set up the non-enumerable functions on the Set prototype object.
133-
InstallGetter($Set.prototype, "size", SetGetSizeJS);
255+
InstallGetter($Set.prototype, "size", SetGetSize);
134256
InstallFunctions($Set.prototype, DONT_ENUM, $Array(
135-
"add", SetAddJS,
136-
"has", SetHasJS,
137-
"delete", SetDeleteJS,
257+
"add", SetAdd,
258+
"has", SetHas,
259+
"delete", SetDelete,
138260
"clear", SetClearJS,
139261
"forEach", SetForEach
140262
));
@@ -169,16 +291,21 @@ function MapConstructor(iterable) {
169291
}
170292

171293

172-
function MapGetJS(key) {
294+
function MapGet(key) {
173295
if (!IS_MAP(this)) {
174296
throw MakeTypeError('incompatible_method_receiver',
175297
['Map.prototype.get', this]);
176298
}
177-
return %_MapGet(this, key);
299+
var table = %_JSCollectionGetTable(this);
300+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
301+
var hash = GetHash(key);
302+
var entry = MapFindEntry(table, numBuckets, key, hash);
303+
if (entry === NOT_FOUND) return UNDEFINED;
304+
return ORDERED_HASH_MAP_VALUE_AT(table, entry, numBuckets);
178305
}
179306

180307

181-
function MapSetJS(key, value) {
308+
function MapSet(key, value) {
182309
if (!IS_MAP(this)) {
183310
throw MakeTypeError('incompatible_method_receiver',
184311
['Map.prototype.set', this]);
@@ -190,34 +317,84 @@ function MapSetJS(key, value) {
190317
if (key === 0) {
191318
key = 0;
192319
}
193-
return %_MapSet(this, key, value);
320+
321+
var table = %_JSCollectionGetTable(this);
322+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
323+
var hash = GetHash(key);
324+
var entry = MapFindEntry(table, numBuckets, key, hash);
325+
if (entry !== NOT_FOUND) {
326+
var existingIndex = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets);
327+
FIXED_ARRAY_SET(table, existingIndex + 1, value);
328+
return this;
329+
}
330+
331+
var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
332+
var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
333+
var capacity = numBuckets << 1;
334+
if ((nof + nod) >= capacity) {
335+
// Need to grow, bail out to runtime.
336+
%MapGrow(this);
337+
// Re-load state from the grown backing store.
338+
table = %_JSCollectionGetTable(this);
339+
numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
340+
nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
341+
nod = ORDERED_HASH_TABLE_DELETED_COUNT(table);
342+
}
343+
entry = nof + nod;
344+
var index = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets);
345+
var bucket = ORDERED_HASH_TABLE_HASH_TO_BUCKET(hash, numBuckets);
346+
var chainEntry = ORDERED_HASH_TABLE_BUCKET_AT(table, bucket);
347+
ORDERED_HASH_TABLE_SET_BUCKET_AT(table, bucket, entry);
348+
ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof + 1);
349+
FIXED_ARRAY_SET(table, index, key);
350+
FIXED_ARRAY_SET(table, index + 1, value);
351+
FIXED_ARRAY_SET(table, index + 2, chainEntry);
352+
return this;
194353
}
195354

196355

197-
function MapHasJS(key) {
356+
function MapHas(key) {
198357
if (!IS_MAP(this)) {
199358
throw MakeTypeError('incompatible_method_receiver',
200359
['Map.prototype.has', this]);
201360
}
202-
return %_MapHas(this, key);
361+
var table = %_JSCollectionGetTable(this);
362+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
363+
var hash = GetHash(key);
364+
return MapFindEntry(table, numBuckets, key, hash) !== NOT_FOUND;
203365
}
204366

205367

206-
function MapDeleteJS(key) {
368+
function MapDelete(key) {
207369
if (!IS_MAP(this)) {
208370
throw MakeTypeError('incompatible_method_receiver',
209371
['Map.prototype.delete', this]);
210372
}
211-
return %_MapDelete(this, key);
373+
var table = %_JSCollectionGetTable(this);
374+
var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
375+
var hash = GetHash(key);
376+
var entry = MapFindEntry(table, numBuckets, key, hash);
377+
if (entry === NOT_FOUND) return false;
378+
379+
var nof = ORDERED_HASH_TABLE_ELEMENT_COUNT(table) - 1;
380+
var nod = ORDERED_HASH_TABLE_DELETED_COUNT(table) + 1;
381+
var index = ORDERED_HASH_MAP_ENTRY_TO_INDEX(entry, numBuckets);
382+
FIXED_ARRAY_SET(table, index, %_TheHole());
383+
FIXED_ARRAY_SET(table, index + 1, %_TheHole());
384+
ORDERED_HASH_TABLE_SET_ELEMENT_COUNT(table, nof);
385+
ORDERED_HASH_TABLE_SET_DELETED_COUNT(table, nod);
386+
if (nof < (numBuckets >>> 1)) %MapShrink(this);
387+
return true;
212388
}
213389

214390

215-
function MapGetSizeJS() {
391+
function MapGetSize() {
216392
if (!IS_MAP(this)) {
217393
throw MakeTypeError('incompatible_method_receiver',
218394
['Map.prototype.size', this]);
219395
}
220-
return %_MapGetSize(this);
396+
var table = %_JSCollectionGetTable(this);
397+
return ORDERED_HASH_TABLE_ELEMENT_COUNT(table);
221398
}
222399

223400

@@ -271,15 +448,20 @@ function SetUpMap() {
271448
%FunctionSetLength(MapForEach, 1);
272449

273450
// Set up the non-enumerable functions on the Map prototype object.
274-
InstallGetter($Map.prototype, "size", MapGetSizeJS);
451+
InstallGetter($Map.prototype, "size", MapGetSize);
275452
InstallFunctions($Map.prototype, DONT_ENUM, $Array(
276-
"get", MapGetJS,
277-
"set", MapSetJS,
278-
"has", MapHasJS,
279-
"delete", MapDeleteJS,
453+
"get", MapGet,
454+
"set", MapSet,
455+
"has", MapHas,
456+
"delete", MapDelete,
280457
"clear", MapClearJS,
281458
"forEach", MapForEach
282459
));
460+
461+
$MapGet = MapGet;
462+
$MapSet = MapSet;
283463
}
284464

285465
SetUpMap();
466+
467+
})();

0 commit comments

Comments
 (0)