@jawis/shared-page-heap
Version:
Heap for concurrent programs.
215 lines (214 loc) • 8.02 kB
JavaScript
"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);