UNPKG

@jawis/shared-page-heap

Version:
215 lines (214 loc) 8.02 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SharedListHeap = void 0; const _jab_1 = require("^jab"); const _shared_algs_1 = require("^shared-algs"); const ALLOC_COUNT_OFFSET = 0; //number of allocated chunks. const FREE_HEAD_OFFSET = ALLOC_COUNT_OFFSET + 1; const STATIC_META_DATA_LENGTH = FREE_HEAD_OFFSET + 1; //trouble, if not multiple of 8. const STATIC_META_DATA_BYTES = Int32Array.BYTES_PER_ELEMENT * STATIC_META_DATA_LENGTH; const NODE_ALLOCATED = 0x7ffffffe; //also in use: INT32_UNDEFINED /** * - Fixed size doubly linked list * - Links are kept in the header section * - Pages contains only user data, so dataSize === pageSize * - Must initialize all memory itself, because this is the most basic data structure and the sharedArray could be reused. * * impl * - Pages are 0-indexed * - It's possible to determine if a page is allocated because NODE_ALLOCATED is stored in its `next slot` * * notes * - Free list only need to be a singly linked list. * * memory layout * meta data * 4 bytes alloc count * 4 bytes free list head * repeated n times * 4 bytes ref to next page * 4 bytes padding (optional depending on n) * n pages (sequentially in memory) * * page layout * x bytes data * * structure of reference (heighest bits first) * 32 bits page index * */ class SharedListHeap { /** * */ constructor(deps) { this.deps = deps; /** * partial */ this.pack = () => { return { maxSize: this.deps.maxSize, dataSize: this.deps.dataSize, sharedArray: this.deps.sharedArray, verifyAfterOperations: this.deps.verifyAfterOperations, initialized: true, }; }; /** * */ this.getPageByteOffset = (pageIndex) => this.META_DATA_BYTES + pageIndex * this.deps.dataSize; /** * */ this.get = (ref, TypedArray) => { (0, _jab_1.assert)(Number.isInteger(this.deps.dataSize / TypedArray.BYTES_PER_ELEMENT)); (0, _jab_1.assert)(0 <= ref && ref < this.deps.maxSize, "Reference isn't valid: ", { ref }); // prettier-ignore if (this.getNext(ref) !== NODE_ALLOCATED) { (0, _jab_1.err)("Reference isn't valid: ", { ref, dataSize: this.deps.dataSize }); } const arr = (0, _jab_1.makeTypedArray)(this.deps.sharedArray, TypedArray, this.getPageByteOffset(ref), this.deps.dataSize / TypedArray.BYTES_PER_ELEMENT); return arr; }; /** * */ this.allocate = (TypedArray, zeroFill) => { const ref = this._allocate(); const array = this.get(ref, TypedArray); if (array instanceof BigInt64Array || array instanceof BigUint64Array) { zeroFill && array.fill(BigInt(0)); } else { zeroFill && array.fill(0); } this.deps.verifyAfterOperations && this._invariant(); return { ref, array }; }; /** * Get a free page from the free list. */ this._allocate = () => { const freeHeadRef = this.deps.sharedArray[FREE_HEAD_OFFSET]; if (freeHeadRef !== _shared_algs_1.INT32_UNDEFINED) { this.deps.sharedArray[FREE_HEAD_OFFSET] = this.getNext(freeHeadRef); this.count++; //mark the node as allocated this.setNext(freeHeadRef, NODE_ALLOCATED); return freeHeadRef; } else { throw new Error("Out of memory"); } }; /** * * - The deallocated node becomes the new free head. * */ this.deallocate = (ref) => { (0, _jab_1.assert)(0 <= ref && ref < this.deps.maxSize, "ref out of bounds: ", { ref }); if (this.getNext(ref) !== NODE_ALLOCATED) { (0, _jab_1.err)("Reference isn't valid: ", { ref }); } this.count--; this.pushFreeNode(ref); this.deps.verifyAfterOperations && this._invariant(); }; /** * */ this.pushFreeNode = (pageIndex) => { // set free head as the next this.setNext(pageIndex, this.deps.sharedArray[FREE_HEAD_OFFSET]); // the node becomes the new free head. this.deps.sharedArray[FREE_HEAD_OFFSET] = pageIndex; }; /** * */ this.getNext = (ref) => { (0, _jab_1.assert)(0 <= ref && ref < this.deps.maxSize, "ref out of bounds: ", { ref }); return this.deps.sharedArray[STATIC_META_DATA_BYTES / 4 + ref]; }; /** * */ this.setNext = (ref, value) => { (0, _jab_1.assert)(0 <= ref && ref < this.deps.maxSize, "ref out of bounds: ", { ref }); this.deps.sharedArray[STATIC_META_DATA_BYTES / 4 + ref] = value; }; /** * */ this._invariant = () => { let node = this.deps.sharedArray[FREE_HEAD_OFFSET]; let freeCount = 0; const freeNodes = []; while (node !== _shared_algs_1.INT32_UNDEFINED) { freeCount++; freeNodes.push(node); node = this.getNext(node); //safe breaker if (freeCount > this.deps.maxSize) { (0, _jab_1.err)("Impossible: free count to high", this.deps.sharedArray); } } (0, _jab_1.assertEq)(this.count + freeCount, this.deps.maxSize); //check allocated nodes for (let i = 0; i < this.deps.maxSize; i++) { if (freeNodes.includes(i)) { continue; } if (this.getNext(i) !== NODE_ALLOCATED) { (0, _jab_1.err)("Reference isn't valid: ", { ref: i }); } } }; /** * */ this.toString = () => { throw new Error("not impl"); }; (0, _jab_1.assert)(this.deps.dataSize > 0, "Data size must be positive: " + this.deps.dataSize); // prettier-ignore (0, _jab_1.assert)(this.deps.maxSize > 0, "Max size must be positive: " + this.deps.maxSize); // prettier-ignore (0, _jab_1.assert)(Number.isInteger(this.deps.dataSize / 4), "data size must be multiple of 4, was: " + this.deps.dataSize); // prettier-ignore this.dataSize = deps.dataSize; //check byte size const byteSize = SharedListHeap.getExpectedByteSize(this.deps.maxSize, this.deps.dataSize); // prettier-ignore (0, _jab_1.assertEq)(this.deps.sharedArray.byteLength, byteSize, "The given sharedArray has wrong size."); // prettier-ignore //trouble, if not multiple of 8. const _maxSize = (this.deps.maxSize / 2) * 2; //rounded up, evenly. this.META_DATA_BYTES = STATIC_META_DATA_BYTES + _maxSize * 4; //state if (!deps.initialized) { this.deps.sharedArray[ALLOC_COUNT_OFFSET] = 0; this.deps.sharedArray[FREE_HEAD_OFFSET] = 0; //loop through all the pages and setup next links. for (let i = 0; i < this.deps.maxSize; i++) { const next = i === this.deps.maxSize - 1 ? _shared_algs_1.INT32_UNDEFINED : i + 1; this.setNext(i, next); } } } /** * */ get count() { return this.deps.sharedArray[ALLOC_COUNT_OFFSET]; } /** * */ set count(value) { this.deps.sharedArray[ALLOC_COUNT_OFFSET] = value; } } exports.SharedListHeap = SharedListHeap; /** * */ SharedListHeap.getExpectedByteSize = (n, dataSize) => STATIC_META_DATA_BYTES + (n / 2) * 2 * (dataSize + 4);