UNPKG

reign

Version:

A persistent, typed-objects implementation.

644 lines (544 loc) 20 kB
"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); }