| Index: src/collection.js
|
| diff --git a/src/collection.js b/src/collection.js
|
| index c8cf639f97d96c3396f463a97ad5181ac4ccc347..1fe083c2a8c450574e384c7f8c2c0f3dd8df9938 100644
|
| --- a/src/collection.js
|
| +++ b/src/collection.js
|
| @@ -12,6 +12,87 @@ var $Set = global.Set;
|
| var $Map = global.Map;
|
|
|
|
|
| +(function() {
|
| + "use strict";
|
| +
|
| + function HashToBucket(hash, numBuckets) {
|
| + return hash & (numBuckets - 1);
|
| + }
|
| + %SetInlineBuiltinFlag(HashToBucket);
|
| +
|
| +
|
| + function HashToEntry(table, hash, numBuckets) {
|
| + var bucket = HashToBucket(hash, numBuckets);
|
| + return %_FixedArrayGet(table, HASH_TABLE_START_INDEX + bucket);
|
| + }
|
| + %SetInlineBuiltinFlag(HashToEntry);
|
| +
|
| +
|
| + function SetFindEntry(table, numBuckets, key, hash) {
|
| + var keyIsNaN = IS_NUMBER(key) && NUMBER_IS_NAN(key);
|
| + for (var entry = HashToEntry(table, hash, numBuckets);
|
| + entry !== NOT_FOUND;
|
| + entry = SET_CHAIN_AT(table, entry, numBuckets)) {
|
| + var candidate = SET_KEY_AT(table, entry, numBuckets);
|
| + if (key === candidate) {
|
| + return entry;
|
| + }
|
| + if (keyIsNaN && IS_NUMBER(candidate) && NUMBER_IS_NAN(candidate)) {
|
| + return entry;
|
| + }
|
| + }
|
| + return NOT_FOUND;
|
| + }
|
| + %SetInlineBuiltinFlag(SetFindEntry);
|
| +
|
| +
|
| + function MapFindEntry(table, numBuckets, key, hash) {
|
| + var keyIsNaN = IS_NUMBER(key) && NUMBER_IS_NAN(key);
|
| + for (var entry = HashToEntry(table, hash, numBuckets);
|
| + entry !== NOT_FOUND;
|
| + entry = MAP_CHAIN_AT(table, entry, numBuckets)) {
|
| + var candidate = MAP_KEY_AT(table, entry, numBuckets);
|
| + if (key === candidate) {
|
| + return entry;
|
| + }
|
| + if (keyIsNaN && IS_NUMBER(candidate) && NUMBER_IS_NAN(candidate)) {
|
| + return entry;
|
| + }
|
| + }
|
| + return NOT_FOUND;
|
| + }
|
| + %SetInlineBuiltinFlag(MapFindEntry);
|
| +
|
| +
|
| + function ComputeIntegerHash(key, seed) {
|
| + var hash = key;
|
| + hash = hash ^ seed;
|
| + hash = ~hash + (hash << 15); // hash = (hash << 15) - hash - 1;
|
| + hash = hash ^ (hash >>> 12);
|
| + hash = hash + (hash << 2);
|
| + hash = hash ^ (hash >>> 4);
|
| + hash = (hash * 2057) | 0; // hash = (hash + (hash << 3)) + (hash << 11);
|
| + hash = hash ^ (hash >>> 16);
|
| + return hash;
|
| + }
|
| + %SetInlineBuiltinFlag(ComputeIntegerHash);
|
| +
|
| +
|
| + function GetHash(key) {
|
| + if (%_IsSmi(key)) {
|
| + return ComputeIntegerHash(key, 0);
|
| + }
|
| + if (IS_STRING(key)) {
|
| + var field = %_StringGetRawHashField(key);
|
| + if ((field & HASH_NOT_COMPUTED_MASK) === 0) {
|
| + return (field >>> STRING_HASH_SHIFT);
|
| + }
|
| + }
|
| + return %GenericHash(key);
|
| + }
|
| + %SetInlineBuiltinFlag(GetHash);
|
| +
|
| +
|
| // -------------------------------------------------------------------
|
| // Harmony Set
|
|
|
| @@ -35,7 +116,7 @@ function SetConstructor(iterable) {
|
| }
|
|
|
|
|
| -function SetAddJS(key) {
|
| +function SetAdd(key) {
|
| if (!IS_SET(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Set.prototype.add', this]);
|
| @@ -44,37 +125,76 @@ function SetAddJS(key) {
|
| // Even though we use SameValueZero as the comparison for the keys we don't
|
| // want to ever store -0 as the key since the key is directly exposed when
|
| // doing iteration.
|
| - if (key === 0) {
|
| + if (IS_NUMBER(key) && key === 0) {
|
| key = 0;
|
| }
|
| - return %_SetAdd(this, key);
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + if (SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND) return this;
|
| +
|
| + var nof = %_FixedArrayGet(table, NUMBER_OF_ELEMENTS_INDEX);
|
| + var nod = %_FixedArrayGet(table, NUMBER_OF_DELETED_ELEMENTS_INDEX);
|
| + var capacity = numBuckets << 1;
|
| + if ((nof + nod) >= capacity) {
|
| + // Need to grow, bail out to runtime.
|
| + %SetGrow(this);
|
| + // Re-enter...TODO(adamk) Fix this
|
| + return %_CallFunction(this, key, SetAdd);
|
| + }
|
| + var entry = nof + nod;
|
| + var index = SET_ENTRY_TO_INDEX(entry, numBuckets);
|
| + var bucket = HashToBucket(hash, numBuckets);
|
| + var chainEntry = %_FixedArrayGet(table, HASH_TABLE_START_INDEX + bucket);
|
| + %_FixedArraySet(table, HASH_TABLE_START_INDEX + bucket, entry | 0);
|
| + %_FixedArraySet(table, index + SET_CHAIN_OFFSET, chainEntry | 0);
|
| + %_FixedArraySet(table, NUMBER_OF_ELEMENTS_INDEX, (nof + 1) | 0);
|
| + %_FixedArraySet(table, index, key);
|
| + return this;
|
| }
|
|
|
|
|
| -function SetHasJS(key) {
|
| +function SetHas(key) {
|
| if (!IS_SET(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Set.prototype.has', this]);
|
| }
|
| - return %_SetHas(this, key);
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + return SetFindEntry(table, numBuckets, key, hash) !== NOT_FOUND;
|
| }
|
|
|
|
|
| -function SetDeleteJS(key) {
|
| +function SetDelete(key) {
|
| if (!IS_SET(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Set.prototype.delete', this]);
|
| }
|
| - return %_SetDelete(this, key);
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + var entry = SetFindEntry(table, numBuckets, key, hash);
|
| + if (entry === NOT_FOUND) return false;
|
| +
|
| + var nof = %_FixedArrayGet(table, NUMBER_OF_ELEMENTS_INDEX) - 1;
|
| + var nod = %_FixedArrayGet(table, NUMBER_OF_DELETED_ELEMENTS_INDEX) + 1;
|
| + var index = SET_ENTRY_TO_INDEX(entry, numBuckets);
|
| + %_FixedArraySet(table, index, %_TheHole());
|
| + %_FixedArraySet(table, NUMBER_OF_ELEMENTS_INDEX, nof | 0);
|
| + %_FixedArraySet(table, NUMBER_OF_DELETED_ELEMENTS_INDEX, nod | 0);
|
| + if (nof < (numBuckets >>> 1)) %SetShrink(this);
|
| + return true;
|
| }
|
|
|
|
|
| -function SetGetSizeJS() {
|
| +function SetGetSize() {
|
| if (!IS_SET(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Set.prototype.size', this]);
|
| }
|
| - return %_SetGetSize(this);
|
| + var table = %_JSCollectionGetTable(this);
|
| + return %_FixedArrayGet(table, NUMBER_OF_ELEMENTS_INDEX);
|
| }
|
|
|
|
|
| @@ -130,11 +250,11 @@ function SetUpSet() {
|
| %FunctionSetLength(SetForEach, 1);
|
|
|
| // Set up the non-enumerable functions on the Set prototype object.
|
| - InstallGetter($Set.prototype, "size", SetGetSizeJS);
|
| + InstallGetter($Set.prototype, "size", SetGetSize);
|
| InstallFunctions($Set.prototype, DONT_ENUM, $Array(
|
| - "add", SetAddJS,
|
| - "has", SetHasJS,
|
| - "delete", SetDeleteJS,
|
| + "add", SetAdd,
|
| + "has", SetHas,
|
| + "delete", SetDelete,
|
| "clear", SetClearJS,
|
| "forEach", SetForEach
|
| ));
|
| @@ -169,16 +289,21 @@ function MapConstructor(iterable) {
|
| }
|
|
|
|
|
| -function MapGetJS(key) {
|
| +function MapGet(key) {
|
| if (!IS_MAP(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Map.prototype.get', this]);
|
| }
|
| - return %_MapGet(this, key);
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + var entry = MapFindEntry(table, numBuckets, key, hash);
|
| + if (entry === NOT_FOUND) return UNDEFINED;
|
| + return MAP_VALUE_AT(table, entry, numBuckets);
|
| }
|
|
|
|
|
| -function MapSetJS(key, value) {
|
| +function MapSet(key, value) {
|
| if (!IS_MAP(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Map.prototype.set', this]);
|
| @@ -187,37 +312,84 @@ function MapSetJS(key, value) {
|
| // Even though we use SameValueZero as the comparison for the keys we don't
|
| // want to ever store -0 as the key since the key is directly exposed when
|
| // doing iteration.
|
| - if (key === 0) {
|
| + if (IS_NUMBER(key) && key === 0) {
|
| key = 0;
|
| }
|
| - return %_MapSet(this, key, value);
|
| +
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + var entry = MapFindEntry(table, numBuckets, key, hash);
|
| + if (entry !== NOT_FOUND) {
|
| + var existingIndex = MAP_ENTRY_TO_INDEX(entry, numBuckets);
|
| + %_FixedArraySet(table, existingIndex + 1, value);
|
| + return this;
|
| + }
|
| +
|
| + var nof = %_FixedArrayGet(table, NUMBER_OF_ELEMENTS_INDEX);
|
| + var nod = %_FixedArrayGet(table, NUMBER_OF_DELETED_ELEMENTS_INDEX);
|
| + var capacity = numBuckets << 1;
|
| + if ((nof + nod) >= capacity) {
|
| + // Need to grow, bail out to runtime.
|
| + %MapGrow(this);
|
| + // Re-enter...TODO(adamk) Fix this
|
| + return %_CallFunction(this, key, value, MapSet);
|
| + }
|
| + entry = nof + nod;
|
| + var index = MAP_ENTRY_TO_INDEX(entry, numBuckets);
|
| + var bucket = HashToBucket(hash, numBuckets);
|
| + var chainEntry = %_FixedArrayGet(table, HASH_TABLE_START_INDEX + bucket);
|
| + %_FixedArraySet(table, HASH_TABLE_START_INDEX + bucket, entry | 0);
|
| + %_FixedArraySet(table, index + MAP_CHAIN_OFFSET, chainEntry | 0);
|
| + %_FixedArraySet(table, NUMBER_OF_ELEMENTS_INDEX, (nof + 1) | 0);
|
| + %_FixedArraySet(table, index, key);
|
| + %_FixedArraySet(table, index + 1, value);
|
| + return this;
|
| }
|
|
|
|
|
| -function MapHasJS(key) {
|
| +function MapHas(key) {
|
| if (!IS_MAP(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Map.prototype.has', this]);
|
| }
|
| - return %_MapHas(this, key);
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + return MapFindEntry(table, numBuckets, key, hash) !== NOT_FOUND;
|
| }
|
|
|
|
|
| -function MapDeleteJS(key) {
|
| +function MapDelete(key) {
|
| if (!IS_MAP(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Map.prototype.delete', this]);
|
| }
|
| - return %_MapDelete(this, key);
|
| + var table = %_JSCollectionGetTable(this);
|
| + var numBuckets = %_FixedArrayGet(table, NUMBER_OF_BUCKETS_INDEX);
|
| + var hash = GetHash(key);
|
| + var entry = MapFindEntry(table, numBuckets, key, hash);
|
| + if (entry === NOT_FOUND) return false;
|
| +
|
| + var nof = %_FixedArrayGet(table, NUMBER_OF_ELEMENTS_INDEX) - 1;
|
| + var nod = %_FixedArrayGet(table, NUMBER_OF_DELETED_ELEMENTS_INDEX) + 1;
|
| + var index = MAP_ENTRY_TO_INDEX(entry, numBuckets);
|
| + %_FixedArraySet(table, index, %_TheHole());
|
| + %_FixedArraySet(table, index + 1, %_TheHole());
|
| + %_FixedArraySet(table, NUMBER_OF_ELEMENTS_INDEX, nof | 0);
|
| + %_FixedArraySet(table, NUMBER_OF_DELETED_ELEMENTS_INDEX, nod | 0);
|
| + if (nof < (numBuckets >>> 1)) %MapShrink(this);
|
| + return true;
|
| }
|
|
|
|
|
| -function MapGetSizeJS() {
|
| +function MapGetSize() {
|
| if (!IS_MAP(this)) {
|
| throw MakeTypeError('incompatible_method_receiver',
|
| ['Map.prototype.size', this]);
|
| }
|
| - return %_MapGetSize(this);
|
| + var table = %_JSCollectionGetTable(this);
|
| + return %_FixedArrayGet(table, NUMBER_OF_ELEMENTS_INDEX);
|
| }
|
|
|
|
|
| @@ -271,15 +443,17 @@ function SetUpMap() {
|
| %FunctionSetLength(MapForEach, 1);
|
|
|
| // Set up the non-enumerable functions on the Map prototype object.
|
| - InstallGetter($Map.prototype, "size", MapGetSizeJS);
|
| + InstallGetter($Map.prototype, "size", MapGetSize);
|
| InstallFunctions($Map.prototype, DONT_ENUM, $Array(
|
| - "get", MapGetJS,
|
| - "set", MapSetJS,
|
| - "has", MapHasJS,
|
| - "delete", MapDeleteJS,
|
| + "get", MapGet,
|
| + "set", MapSet,
|
| + "has", MapHas,
|
| + "delete", MapDelete,
|
| "clear", MapClearJS,
|
| "forEach", MapForEach
|
| ));
|
| }
|
|
|
| SetUpMap();
|
| +
|
| +})();
|
|
|