reign
Version:
A persistent, typed-objects implementation.
613 lines (524 loc) • 18.5 kB
JavaScript
"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);
}