reign
Version:
A persistent, typed-objects implementation.
644 lines (544 loc) • 20 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
exports.make = make;
var _backing = require("backing");
var _backing2 = _interopRequireDefault(_backing);
var _symbols = require("../symbols");
var _performance = require("../performance");
var _ = require("../");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var HEADER_SIZE = 16;
var ARRAY_POINTER_OFFSET = 0;
var ARRAY_LENGTH_OFFSET = 8;
var CARDINALITY_OFFSET = 12;
var INITIAL_BUCKET_COUNT = 4096;
var TYPE_ASCII = 'TYPE_ASCII';
var TYPE_CHAR_ARRAY = 'TYPE_CHAR_ARRAY';
var STRING_LENGTH_OFFSET = 0;
var STRING_HASH_OFFSET = 4;
var STRING_HEADER_SIZE = 8;
var STRING_DATA_OFFSET = STRING_HEADER_SIZE;
function make(realm, poolPointerAddress) {
var TypeClass = realm.TypeClass;
var StringType = realm.StringType;
var backing = realm.backing;
var StringPool = function () {
function StringPool(input, address) {
_classCallCheck(this, StringPool);
if (!(input instanceof _backing2.default)) {
input = backing;
address = backing.calloc(HEADER_SIZE);
createPool(backing, address);
}
// Issue 252
this[_symbols.$Backing] = input;
// Issue 252
this[_symbols.$Address] = address;
}
_createClass(StringPool, [{
key: "hash",
/**
* Return the hash code for the given string.
*/
value: function hash(input) {
return hashString(input);
}
/**
* Gets the address of the given string, if it exists, otherwise 0.
*/
}, {
key: "get",
value: function get(input) {
var hash = 0x811c9dc5;
var allAscii = true;
for (var i = 0; i < input.length; i++) {
var code = input.charCodeAt(i);
if (code > 127) {
allAscii = false;
}
hash ^= code;
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
hash = hash >>> 0;
// Issue 252
var backing = this[_symbols.$Backing];
// Issue 252
return lookupString(backing, this[_symbols.$Address], input, hash, allAscii);
}
/**
* Adds the given string to the pool if it does not already exist, and returns its address.
* Note that adding a string to the pool will increment the string's reference count by 1.
*/
}, {
key: "add",
value: function add(input) {
// Issue 252
return createString(this[_symbols.$Backing], this[_symbols.$Address], '' + input);
}
/**
* Remove the given string from the pool *if* its reference count is 1,
* otherwise decrement the reference count by one.
*
* Returns `true` if the string was actually removed, otherwise `false`.
*/
}, {
key: "remove",
value: function remove(input) {
var hash = 0x811c9dc5;
var allAscii = true;
for (var i = 0; i < input.length; i++) {
var code = input.charCodeAt(i);
if (code > 127) {
allAscii = false;
}
hash ^= code;
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
hash = hash >>> 0;
// Issue 252
return removeString(this[_symbols.$Backing], this[_symbols.$Address], input, hash, allAscii);
}
/**
* Decrement the reference count of a string at the given address.
*/
}, {
key: "unref",
value: function unref(address) {
// Issue 252
return unrefString(this[_symbols.$Backing], this[_symbols.$Address], address);
}
/**
* Determines whether the given string exists in the pool.
*/
}, {
key: "has",
value: function has(input) {
var hash = 0x811c9dc5;
var allAscii = true;
for (var i = 0; i < input.length; i++) {
var code = input.charCodeAt(i);
if (code > 127) {
allAscii = false;
}
hash ^= code;
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
hash = hash >>> 0;
// Issue 252
return lookupString(this[_symbols.$Backing], this[_symbols.$Address], input, hash, allAscii) !== 0;
}
}, {
key: "size",
get: function get() {
// Issue 252
return getCardinality(this[_symbols.$Backing], this[_symbols.$Address]);
}
}]);
return StringPool;
}();
/**
* Create a string pool at the given address.
*/
function createPool(backing, address) {
var pointerArrayLength = INITIAL_BUCKET_COUNT;
var pointerArrayAddress = backing.calloc(pointerArrayLength * 8);
setArrayAddress(backing, address, pointerArrayAddress);
setArrayLength(backing, address, pointerArrayLength);
setCardinality(backing, address, 0);
}
function getArrayAddress(backing, address) {
return backing.getFloat64(address);
}
function setArrayAddress(backing, address, value) {
backing.setFloat64(address, value);
}
function getArrayLength(backing, address) {
return backing.getUint32(address + ARRAY_LENGTH_OFFSET);
}
function setArrayLength(backing, address, value) {
backing.setUint32(address + ARRAY_LENGTH_OFFSET, value);
}
function getCardinality(backing, address) {
return backing.getUint32(address + CARDINALITY_OFFSET);
}
function setCardinality(backing, address, value) {
backing.setUint32(address + CARDINALITY_OFFSET, value);
}
/**
* Read the hash for the given string.
*/
function getStringHash(backing, address) {
return backing.getUint32(address + STRING_HASH_OFFSET);
}
(0, _performance.forceInline)(getStringHash);
/**
* Write the hash for the given string.
*/
function setStringHash(backing, address, hash) {
return backing.setUint32(address + STRING_HASH_OFFSET, hash);
}
(0, _performance.forceInline)(setStringHash);
/**
* Read the number of characters in the string at the given address.
*/
function getNumberOfCharacters(backing, address) {
return Math.abs(backing.getInt32(address)); // STRING_LENGTH_OFFSET === 0 so no need to add.
}
(0, _performance.forceInline)(getNumberOfCharacters);
/**
* Read the string at the given address.
*/
function getString(backing, address) {
if (address === 0) {
return '';
}
var arena = backing.arenaFor(address);
var offset = backing.offsetFor(address);
var length = arena.int32Array[offset >> 2];
if (length < 0) {
offset = offset + STRING_DATA_OFFSET >> 1;
return String.fromCharCode.apply(String, _toConsumableArray(arena.uint16Array.slice(offset, offset + Math.abs(length))));
} else {
offset = offset + STRING_DATA_OFFSET;
return String.fromCharCode.apply(String, _toConsumableArray(arena.uint8Array.slice(offset, offset + Math.abs(length))));
}
}
(0, _performance.forceInline)(getString);
/**
* Check that the string stored at the given address matches the given input + hash.
*/
function checkEqual(backing, address, input, hash, allAscii) {
if (getStringHash(backing, address) !== hash) {
return false;
}
var length = backing.getInt32(address);
if (length < 0) {
if (allAscii) {
return false;
}
length = -length;
if (length !== input.length) {
return false;
}
var arena = backing.arenaFor(address);
var chars = arena.uint16Array;
var offset = backing.offsetFor(address + STRING_HEADER_SIZE) >> 1;
for (var i = 0; i < length; i++) {
if (input.charCodeAt(i) !== chars[offset + i]) {
return false;
}
}
return true;
} else {
if (!allAscii) {
return false;
} else if (length !== input.length) {
return false;
}
var _arena = backing.arenaFor(address);
var _chars = _arena.uint8Array;
var _offset = backing.offsetFor(address + STRING_HEADER_SIZE);
for (var _i = 0; _i < length; _i++) {
if (input.charCodeAt(_i) !== _chars[_offset + _i]) {
return false;
}
}
return true;
}
}
(0, _performance.forceInline)(checkEqual);
/**
* Store the given string, intern it and return the address.
* If a string already exists in the pool, the existing string's address
* will be returned and no duplicate will be created.
*/
function createString(backing, poolAddress, input) {
var hash = 0x811c9dc5;
var allAscii = true;
var length = input.length;
for (var i = 0; i < length; i++) {
var code = input.charCodeAt(i);
if (code > 127) {
allAscii = false;
}
hash ^= code;
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
hash = hash >>> 0;
return lookupOrInsertString(backing, poolAddress, input, hash, allAscii);
}
(0, _performance.forceInline)(createString);
/**
* Store the given raw string and return the address.
* The string will NOT be interned.
*/
function createRawString(backing, input) {
var hash = 0x811c9dc5;
var allAscii = true;
var length = input.length;
for (var i = 0; i < length; i++) {
var code = input.charCodeAt(i);
if (code > 127) {
allAscii = false;
}
hash ^= code;
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
hash = hash >>> 0;
return storeString(backing, input, hash, allAscii);
}
(0, _performance.forceInline)(createRawString);
/**
* Returns the hash for the given string.
*/
function hashString(input) {
var hash = 0x811c9dc5;
for (var i = 0; i < input.length; i++) {
hash ^= input.charCodeAt(i);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return hash >>> 0;
}
(0, _performance.forceInline)(hashString);
/**
* Return the appropriate bucket for the given input + hash.
*/
function probe(backing, poolAddress, input, hash, allAscii) {
var pointerArrayLength = getArrayLength(backing, poolAddress);
var pointerArrayAddress = getArrayAddress(backing, poolAddress);
var arena = backing.arenaFor(pointerArrayAddress);
var float64Array = arena.float64Array;
var startOffset = backing.offsetFor(pointerArrayAddress) >> 3;
var index = hash & pointerArrayLength - 1;
var offset = startOffset + index;
var address = 0;
while ((address = float64Array[offset]) !== 0 && !checkEqual(backing, address, input, hash, allAscii)) {
index++;
if (index >= pointerArrayLength) {
index = 0;
}
offset = startOffset + index;
}
return arena.startAddress + (offset << 3);
}
(0, _performance.forceInline)(probe);
/**
* Find the address of the string for the given input + hash, or 0 if it does not exist.
*/
function lookupString(backing, poolAddress, input, hash, allAscii) {
return backing.getFloat64(probe(backing, poolAddress, input, hash, allAscii));
}
(0, _performance.forceInline)(lookupString);
/**
* Find the address of the string for the given input + hash, or create it if it does not exist.
*/
function lookupOrInsertString(backing, poolAddress, input, hash, allAscii) {
var pointerAddress = probe(backing, poolAddress, input, hash, allAscii);
var address = backing.getFloat64(pointerAddress);
if (address !== 0) {
backing.gc.ref(address);
return address;
}
address = storeString(backing, input, hash, allAscii);
backing.setFloat64(pointerAddress, address);
var size = getCardinality(backing, poolAddress) + 1;
setCardinality(backing, poolAddress, size);
var pointerArrayLength = getArrayLength(backing, poolAddress);
if (size + (size >> 2) >= pointerArrayLength) {
resize(backing, poolAddress);
}
return address;
}
(0, _performance.forceInline)(lookupOrInsertString);
/**
* Store a string and return its address.
*/
function storeString(backing, input, hash, allAscii) {
if (allAscii) {
return storeAsciiString(backing, input, hash);
} else {
return storeMultibyteString(backing, input, hash);
}
}
(0, _performance.forceInline)(storeString);
function storeAsciiString(backing, input, hash) {
var length = input.length;
var byteLength = length + STRING_HEADER_SIZE;
var address = backing.gc.alloc(byteLength, 0, 1);
backing.setInt32(address, length);
backing.setUint32(address + STRING_HASH_OFFSET, hash);
var offset = backing.offsetFor(address + STRING_DATA_OFFSET);
var chars = backing.arenaFor(address).uint8Array;
for (var i = 0; i < length; i++) {
chars[offset + i] = input.charCodeAt(i);
}
return address;
}
(0, _performance.forceInline)(storeAsciiString);
function storeMultibyteString(backing, input, hash) {
var length = input.length;
var byteLength = length + length + STRING_HEADER_SIZE;
var address = backing.gc.alloc(byteLength, 0, 1);
backing.setInt32(address, -length);
backing.setUint32(address + STRING_HASH_OFFSET, hash);
var offset = backing.offsetFor(address + STRING_DATA_OFFSET) >> 1;
var chars = backing.arenaFor(address).uint16Array;
for (var i = 0; i < length; i++) {
chars[offset + i] = input.charCodeAt(i);
}
return address;
}
(0, _performance.forceInline)(storeMultibyteString);
/**
* Decrement the reference count for the given string and remove it from the pool if appropriate.
*/
function unrefString(backing, poolAddress, target) {
var hash = getStringHash(backing, target);
var pointerArrayLength = getArrayLength(backing, poolAddress);
var pointerArrayAddress = getArrayAddress(backing, poolAddress);
var arena = backing.arenaFor(pointerArrayAddress);
var float64Array = arena.float64Array;
var startOffset = backing.offsetFor(pointerArrayAddress) >> 3;
var index = hash & pointerArrayLength - 1;
var offset = startOffset + index;
var address = 0;
while ((address = float64Array[offset]) !== 0 && address !== target) {
index++;
if (index >= pointerArrayLength) {
index = 0;
}
offset = startOffset + index;
}
var p = arena.startAddress + (offset << 3);
return removeStringByPointer(backing, poolAddress, p);
}
(0, _performance.forceInline)(unrefString);
/**
* Remove the given input + hash from the hash map.
*/
function removeString(backing, poolAddress, input, hash, allAscii) {
var p = probe(backing, poolAddress, input, hash, allAscii);
return removeStringByPointer(backing, poolAddress, p);
}
(0, _performance.forceInline)(removeString);
/**
* Remove the string at the given address from the hash map.
*/
function removeStringByPointer(backing, poolAddress, p) {
var address = backing.getFloat64(p);
if (address === 0) {
// Item does not exist.
return false;
}
if (backing.gc.unref(address) > 0) {
// Item has other references
return false;
}
var pointerArrayLength = getArrayLength(backing, poolAddress);
var pointerArrayAddress = getArrayAddress(backing, poolAddress);
var end = pointerArrayAddress + pointerArrayLength * 8;
var q = p;
while (true) {
// Move q to the next entry
q = q + 8;
if (q === end) {
q = pointerArrayAddress;
}
var qPointer = backing.getFloat64(q);
// All entries between p and q have their initial position between p and q
// and the entry p can be cleared without breaking the search for these
// entries.
if (qPointer === 0) {
break;
}
var qHash = getStringHash(backing, qPointer);
// Find the initial position for the entry at position q.
var r = pointerArrayAddress + (qHash & pointerArrayLength - 1) * 8;
// If the entry at position q has its initial position outside the range
// between p and q it can be moved forward to position p and will still be
// found. There is now a new candidate entry for clearing.
if (q > p && (r <= p || r > q) || q < p && r <= p && r > q) {
backing.copy(p, q, 8);
p = q;
}
}
// Clear the entry which is allowed to be emptied.
setStringHash(backing, backing.getFloat64(p), 0);
setCardinality(backing, poolAddress, getCardinality(backing, poolAddress) - 1);
return true;
}
(0, _performance.forceInline)(removeStringByPointer);
function resize(backing, poolAddress) {
var pointerArrayLength = getArrayLength(backing, poolAddress);
var pointerArrayAddress = getArrayAddress(backing, poolAddress);
var newPointerArrayLength = pointerArrayLength * 2;
var newPointerArrayAddress = backing.calloc(newPointerArrayLength * 8);
setArrayAddress(backing, poolAddress, newPointerArrayAddress);
setArrayLength(backing, poolAddress, newPointerArrayLength);
var newOffset = backing.offsetFor(newPointerArrayAddress) / 8;
var newPointers = backing.arenaFor(newPointerArrayAddress).float64Array;
var oldOffset = backing.offsetFor(pointerArrayAddress) / 8;
var oldPointers = backing.arenaFor(pointerArrayAddress).float64Array;
for (var oldIndex = 0; oldIndex < pointerArrayLength; oldIndex++) {
var _address = oldPointers[oldOffset + oldIndex];
if (_address === 0) {
continue;
}
var _hash = getStringHash(backing, _address);
var targetIndex = _hash & newPointerArrayLength - 1;
var offset = newOffset + targetIndex;
var pointer = 0;
while (newPointers[offset] !== 0) {
targetIndex++;
if (targetIndex >= newPointerArrayLength) {
targetIndex = 0;
}
offset = newOffset + targetIndex;
}
newPointers[offset] = _address;
}
backing.free(pointerArrayAddress);
}
{
var _address2 = backing.getFloat64(poolPointerAddress);
if (_address2 === 0) {
var pool = new StringPool();
// Issue 252
backing.setFloat64(poolPointerAddress, pool[_symbols.$Address]);
return pool;
} else {
return new StringPool(backing, _address2);
}
}
}
function randomAsciiString() {
var length = Math.floor(Math.random() * 255);
var chars = new Array(length);
var seed = Math.round(Math.random() * 100000);
for (var i = 0; i < length; i++) {
seed = (seed + i * 333) % 127;
if (seed < 32) {
seed += 32;
}
chars[i] = seed;
}
return String.fromCharCode.apply(String, chars);
}
function randomMultiByteString() {
var length = Math.floor(Math.random() * 255);
var chars = new Array(length);
var seed = Math.round(Math.random() * 100000);
for (var i = 0; i < length; i++) {
seed = (seed + i * 333) % 512;
if (seed < 32) {
seed += 32;
}
chars[i] = seed;
}
return String.fromCharCode.apply(String, chars);
}