UNPKG

reign

Version:

A persistent, typed-objects implementation.

613 lines (524 loc) 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 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 }; } const HEADER_SIZE = 16; const ARRAY_POINTER_OFFSET = 0; const ARRAY_LENGTH_OFFSET = 8; const CARDINALITY_OFFSET = 12; const INITIAL_BUCKET_COUNT = 4096; const TYPE_ASCII = 'TYPE_ASCII'; const TYPE_CHAR_ARRAY = 'TYPE_CHAR_ARRAY'; const STRING_LENGTH_OFFSET = 0; const STRING_HASH_OFFSET = 4; const STRING_HEADER_SIZE = 8; const STRING_DATA_OFFSET = STRING_HEADER_SIZE; function make(realm, poolPointerAddress) { const TypeClass = realm.TypeClass; const StringType = realm.StringType; const backing = realm.backing; class StringPool { constructor(input, address) { 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; } get size() { // Issue 252 return getCardinality(this[_symbols.$Backing], this[_symbols.$Address]); } /** * Return the hash code for the given string. */ hash(input) { return hashString(input); } /** * Gets the address of the given string, if it exists, otherwise 0. */ get(input) { let hash = 0x811c9dc5; let allAscii = true; for (let i = 0; i < input.length; i++) { const 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 const 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. */ 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`. */ remove(input) { let hash = 0x811c9dc5; let allAscii = true; for (let i = 0; i < input.length; i++) { const 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. */ unref(address) { // Issue 252 return unrefString(this[_symbols.$Backing], this[_symbols.$Address], address); } /** * Determines whether the given string exists in the pool. */ has(input) { let hash = 0x811c9dc5; let allAscii = true; for (let i = 0; i < input.length; i++) { const 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; } } /** * Create a string pool at the given address. */ function createPool(backing, address) { const pointerArrayLength = INITIAL_BUCKET_COUNT; const 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 ''; } const arena = backing.arenaFor(address); let offset = backing.offsetFor(address); const length = arena.int32Array[offset >> 2]; if (length < 0) { offset = offset + STRING_DATA_OFFSET >> 1; return String.fromCharCode(...arena.uint16Array.slice(offset, offset + Math.abs(length))); } else { offset = offset + STRING_DATA_OFFSET; return String.fromCharCode(...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; } let length = backing.getInt32(address); if (length < 0) { if (allAscii) { return false; } length = -length; if (length !== input.length) { return false; } const arena = backing.arenaFor(address); const chars = arena.uint16Array; const offset = backing.offsetFor(address + STRING_HEADER_SIZE) >> 1; for (let 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; } const arena = backing.arenaFor(address); const chars = arena.uint8Array; const offset = backing.offsetFor(address + STRING_HEADER_SIZE); for (let 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) { let hash = 0x811c9dc5; let allAscii = true; const length = input.length; for (let i = 0; i < length; i++) { const 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) { let hash = 0x811c9dc5; let allAscii = true; const length = input.length; for (let i = 0; i < length; i++) { const 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) { let hash = 0x811c9dc5; for (let 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) { const pointerArrayLength = getArrayLength(backing, poolAddress); const pointerArrayAddress = getArrayAddress(backing, poolAddress); const arena = backing.arenaFor(pointerArrayAddress); const float64Array = arena.float64Array; const startOffset = backing.offsetFor(pointerArrayAddress) >> 3; let index = hash & pointerArrayLength - 1; let offset = startOffset + index; let 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) { const pointerAddress = probe(backing, poolAddress, input, hash, allAscii); let address = backing.getFloat64(pointerAddress); if (address !== 0) { backing.gc.ref(address); return address; } address = storeString(backing, input, hash, allAscii); backing.setFloat64(pointerAddress, address); const size = getCardinality(backing, poolAddress) + 1; setCardinality(backing, poolAddress, size); const 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) { const length = input.length; const byteLength = length + STRING_HEADER_SIZE; const address = backing.gc.alloc(byteLength, 0, 1); backing.setInt32(address, length); backing.setUint32(address + STRING_HASH_OFFSET, hash); const offset = backing.offsetFor(address + STRING_DATA_OFFSET); const chars = backing.arenaFor(address).uint8Array; for (let i = 0; i < length; i++) { chars[offset + i] = input.charCodeAt(i); } return address; } (0, _performance.forceInline)(storeAsciiString); function storeMultibyteString(backing, input, hash) { const length = input.length; const byteLength = length + length + STRING_HEADER_SIZE; const address = backing.gc.alloc(byteLength, 0, 1); backing.setInt32(address, -length); backing.setUint32(address + STRING_HASH_OFFSET, hash); const offset = backing.offsetFor(address + STRING_DATA_OFFSET) >> 1; const chars = backing.arenaFor(address).uint16Array; for (let 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) { const hash = getStringHash(backing, target); const pointerArrayLength = getArrayLength(backing, poolAddress); const pointerArrayAddress = getArrayAddress(backing, poolAddress); const arena = backing.arenaFor(pointerArrayAddress); const float64Array = arena.float64Array; const startOffset = backing.offsetFor(pointerArrayAddress) >> 3; let index = hash & pointerArrayLength - 1; let offset = startOffset + index; let address = 0; while ((address = float64Array[offset]) !== 0 && address !== target) { index++; if (index >= pointerArrayLength) { index = 0; } offset = startOffset + index; } const 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) { let 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) { const 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; } const pointerArrayLength = getArrayLength(backing, poolAddress); const pointerArrayAddress = getArrayAddress(backing, poolAddress); const end = pointerArrayAddress + pointerArrayLength * 8; let q = p; while (true) { // Move q to the next entry q = q + 8; if (q === end) { q = pointerArrayAddress; } const 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; } const qHash = getStringHash(backing, qPointer); // Find the initial position for the entry at position q. const 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) { const pointerArrayLength = getArrayLength(backing, poolAddress); const pointerArrayAddress = getArrayAddress(backing, poolAddress); const newPointerArrayLength = pointerArrayLength * 2; const newPointerArrayAddress = backing.calloc(newPointerArrayLength * 8); setArrayAddress(backing, poolAddress, newPointerArrayAddress); setArrayLength(backing, poolAddress, newPointerArrayLength); const newOffset = backing.offsetFor(newPointerArrayAddress) / 8; const newPointers = backing.arenaFor(newPointerArrayAddress).float64Array; const oldOffset = backing.offsetFor(pointerArrayAddress) / 8; const oldPointers = backing.arenaFor(pointerArrayAddress).float64Array; for (let oldIndex = 0; oldIndex < pointerArrayLength; oldIndex++) { const address = oldPointers[oldOffset + oldIndex]; if (address === 0) { continue; } const hash = getStringHash(backing, address); let targetIndex = hash & newPointerArrayLength - 1; let offset = newOffset + targetIndex; let pointer = 0; while (newPointers[offset] !== 0) { targetIndex++; if (targetIndex >= newPointerArrayLength) { targetIndex = 0; } offset = newOffset + targetIndex; } newPointers[offset] = address; } backing.free(pointerArrayAddress); } { const address = backing.getFloat64(poolPointerAddress); if (address === 0) { const pool = new StringPool(); // Issue 252 backing.setFloat64(poolPointerAddress, pool[_symbols.$Address]); return pool; } else { return new StringPool(backing, address); } } } function randomAsciiString() { const length = Math.floor(Math.random() * 255); const chars = new Array(length); let seed = Math.round(Math.random() * 100000); for (let i = 0; i < length; i++) { seed = (seed + i * 333) % 127; if (seed < 32) { seed += 32; } chars[i] = seed; } return String.fromCharCode(...chars); } function randomMultiByteString() { const length = Math.floor(Math.random() * 255); const chars = new Array(length); let seed = Math.round(Math.random() * 100000); for (let i = 0; i < length; i++) { seed = (seed + i * 333) % 512; if (seed < 32) { seed += 32; } chars[i] = seed; } return String.fromCharCode(...chars); }