malloc
Version:
Simple malloc() & free() implementation on top of buffers and array buffers.
897 lines (765 loc) • 28 kB
JavaScript
/* @flow */
const POINTER_SIZE_IN_BYTES = 4;
const MAX_HEIGHT = 32;
const HEADER_SIZE_IN_QUADS = 1 + (MAX_HEIGHT * 2);
const HEADER_OFFSET_IN_QUADS = 1;
const HEIGHT_OFFSET_IN_QUADS = 0;
const PREV_OFFSET_IN_QUADS = 1;
const NEXT_OFFSET_IN_QUADS = 2;
const POINTER_SIZE_IN_QUADS = 1;
const POINTER_OVERHEAD_IN_QUADS = 2;
const MIN_FREEABLE_SIZE_IN_QUADS = 3;
const FIRST_BLOCK_OFFSET_IN_QUADS = HEADER_OFFSET_IN_QUADS + HEADER_SIZE_IN_QUADS + POINTER_OVERHEAD_IN_QUADS;
const MIN_FREEABLE_SIZE_IN_BYTES = 16;
const FIRST_BLOCK_OFFSET_IN_BYTES = FIRST_BLOCK_OFFSET_IN_QUADS * POINTER_SIZE_IN_BYTES;
const OVERHEAD_IN_BYTES = (FIRST_BLOCK_OFFSET_IN_QUADS + 1) * POINTER_SIZE_IN_BYTES;
const ALIGNMENT_IN_BYTES = 8;
const ALIGNMENT_MASK = ALIGNMENT_IN_BYTES - 1;
const UPDATES: Int32Array = (new Int32Array(MAX_HEIGHT)).fill(HEADER_OFFSET_IN_QUADS);
type ListNode = {
type: string;
offset: int32;
size: int32;
height: int32;
pointers: int32[];
};
type InspectionResult = {
header: ListNode;
blocks: Array<{
type: string;
size: int32;
node?: ListNode
}>;
};
export default class Allocator {
buffer: ArrayBuffer;
byteOffset: uint32;
byteLength: uint32;
int32Array: Int32Array;
/**
* Initialize the allocator from the given Buffer or ArrayBuffer.
*/
constructor (buffer: Buffer|ArrayBuffer, byteOffset: uint32 = 0, byteLength: uint32 = 0) {
pre: {
if (buffer instanceof Buffer) {
byteLength <= buffer.length;
}
else if (buffer instanceof ArrayBuffer) {
byteLength <= buffer.byteLength;
}
}
if (buffer instanceof Buffer) {
this.buffer = buffer.buffer;
this.byteOffset = buffer.byteOffset + byteOffset;
this.byteLength = byteLength === 0 ? buffer.length : byteLength;
}
else if (buffer instanceof ArrayBuffer) {
this.buffer = buffer;
this.byteOffset = byteOffset;
this.byteLength = byteLength === 0 ? buffer.byteLength - byteOffset : byteLength;
}
else {
throw new TypeError(`Expected buffer to be an instance of Buffer or ArrayBuffer`);
}
assert: this.byteLength >= OVERHEAD_IN_BYTES;
this.int32Array = prepare(new Int32Array(this.buffer, this.byteOffset, bytesToQuads(this.byteLength)));
checkListIntegrity(this.int32Array);
}
/**
* Allocate a given number of bytes and return the offset.
* If allocation fails, returns 0.
*/
alloc (numberOfBytes: int32): int32 {
pre: checkListIntegrity(this.int32Array);
post: {
it === 0 || it >= quadsToBytes(FIRST_BLOCK_OFFSET_IN_QUADS);
checkListIntegrity(this.int32Array);
}
numberOfBytes = align(numberOfBytes);
if (numberOfBytes < MIN_FREEABLE_SIZE_IN_BYTES) {
numberOfBytes = MIN_FREEABLE_SIZE_IN_BYTES;
}
else if (numberOfBytes > this.byteLength) {
throw new RangeError(`Allocation size must be between ${MIN_FREEABLE_SIZE_IN_BYTES} bytes and ${this.byteLength - OVERHEAD_IN_BYTES} bytes`);
}
trace: `Allocating ${numberOfBytes} bytes.`;
const minimumSize: int32 = bytesToQuads(numberOfBytes);
const int32Array: Int32Array = this.int32Array;
const block: int32 = findFreeBlock(int32Array, minimumSize);
if (block <= HEADER_OFFSET_IN_QUADS) {
return 0;
}
const blockSize: int32 = readSize(int32Array, block);
assert: {
blockSize >= POINTER_SIZE_IN_QUADS;
blockSize < this.byteLength;
}
if (blockSize - (minimumSize + POINTER_OVERHEAD_IN_QUADS) >= MIN_FREEABLE_SIZE_IN_QUADS) {
split(int32Array, block, minimumSize, blockSize);
}
else {
remove(int32Array, block, blockSize);
}
return quadsToBytes(block);
}
/**
* Allocate and clear the given number of bytes and return the offset.
* If allocation fails, returns 0.
*/
calloc (numberOfBytes: int32): int32 {
post: {
it === 0 || it >= quadsToBytes(FIRST_BLOCK_OFFSET_IN_QUADS);
checkListIntegrity(this.int32Array);
}
if (numberOfBytes < MIN_FREEABLE_SIZE_IN_BYTES) {
numberOfBytes = MIN_FREEABLE_SIZE_IN_BYTES;
}
else {
numberOfBytes = align(numberOfBytes);
}
const address = this.alloc(numberOfBytes);
if (address === 0) {
// Not enough space
return 0;
}
const int32Array = this.int32Array;
const offset = bytesToQuads(address);
const limit = numberOfBytes / 4;
for (let i = 0; i < limit; i++) {
int32Array[offset + i] = 0;
}
return address;
}
/**
* Free a number of bytes from the given address.
*/
free (address: int32): int32 {
pre: checkListIntegrity(this.int32Array);
post: {
it >= 0;
it < quadsToBytes(this.int32Array.length);
checkListIntegrity(this.int32Array);
}
if ((address & ALIGNMENT_MASK) !== 0) {
throw new RangeError(`Address must be a multiple of (${ALIGNMENT_IN_BYTES}).`);
}
if (address < FIRST_BLOCK_OFFSET_IN_BYTES || address > this.byteLength) {
throw new RangeError(`Address must be between ${FIRST_BLOCK_OFFSET_IN_BYTES} and ${this.byteLength - OVERHEAD_IN_BYTES}`);
}
const int32Array: Int32Array = this.int32Array;
const block = bytesToQuads(address);
const blockSize: uint32 = readSize(int32Array, block);
trace: `Freeing ${quadsToBytes(blockSize)} bytes from block ${address}.`;
/* istanbul ignore if */
if (blockSize < MIN_FREEABLE_SIZE_IN_QUADS || blockSize > (this.byteLength - OVERHEAD_IN_BYTES) / 4) {
throw new RangeError(`Invalid block: ${block}, got block size: ${quadsToBytes(blockSize)}`);
}
const preceding: int32 = getFreeBlockBefore(int32Array, block);
const trailing: int32 = getFreeBlockAfter(int32Array, block);
if (preceding !== 0) {
if (trailing !== 0) {
return quadsToBytes(insertMiddle(int32Array, preceding, block, blockSize, trailing));
}
else {
return quadsToBytes(insertAfter(int32Array, preceding, block, blockSize));
}
}
else if (trailing !== 0) {
return quadsToBytes(insertBefore(int32Array, trailing, block, blockSize));
}
else {
return quadsToBytes(insert(int32Array, block, blockSize));
}
}
/**
* Return the size of the block at the given address.
*/
sizeOf (address: int32): uint32 {
if (address < FIRST_BLOCK_OFFSET_IN_BYTES || address > this.byteLength || typeof address !== 'number' || isNaN(address)) {
throw new RangeError(`Address must be between ${FIRST_BLOCK_OFFSET_IN_BYTES} and ${this.byteLength - OVERHEAD_IN_BYTES}`);
}
if ((address & ALIGNMENT_MASK) !== 0) {
throw new RangeError(`Address must be a multiple of the pointer size (${POINTER_SIZE_IN_BYTES}).`);
}
return quadsToBytes(readSize(this.int32Array, bytesToQuads(address)));
}
/**
* Inspect the instance.
*/
inspect (): InspectionResult {
return inspect(this.int32Array);
}
}
/**
* Prepare the given int32Array and ensure it contains a valid header.
*/
export function prepare (int32Array: Int32Array): Int32Array {
if (!verifyHeader(int32Array)) {
writeInitialHeader(int32Array);
}
return int32Array;
}
/**
* Verify that the int32Array contains a valid header.
*/
export function verifyHeader (int32Array: Int32Array): boolean {
return int32Array[HEADER_OFFSET_IN_QUADS - 1] === HEADER_SIZE_IN_QUADS
&& int32Array[HEADER_OFFSET_IN_QUADS + HEADER_SIZE_IN_QUADS] === HEADER_SIZE_IN_QUADS;
}
/**
* Write the initial header for an empty int32Array.
*/
function writeInitialHeader (int32Array: Int32Array) {
trace: `Writing initial header.`;
const header = HEADER_OFFSET_IN_QUADS;
const headerSize = HEADER_SIZE_IN_QUADS;
const block = FIRST_BLOCK_OFFSET_IN_QUADS;
const blockSize = int32Array.length - (header + headerSize + POINTER_OVERHEAD_IN_QUADS + POINTER_SIZE_IN_QUADS);
writeFreeBlockSize(int32Array, headerSize, header);
int32Array[header + HEIGHT_OFFSET_IN_QUADS] = 1;
int32Array[header + NEXT_OFFSET_IN_QUADS] = block;
for (let height = 1; height < MAX_HEIGHT; height++) {
int32Array[header + NEXT_OFFSET_IN_QUADS + height] = HEADER_OFFSET_IN_QUADS;
}
writeFreeBlockSize(int32Array, blockSize, block);
int32Array[block + HEIGHT_OFFSET_IN_QUADS] = 1;
int32Array[block + NEXT_OFFSET_IN_QUADS] = header;
}
/**
* Check the integrity of the freelist in the given array.
*/
export function checkListIntegrity (int32Array: Int32Array): boolean {
let block: int32 = FIRST_BLOCK_OFFSET_IN_QUADS;
while (block < int32Array.length - POINTER_SIZE_IN_QUADS) {
const size: int32 = readSize(int32Array, block);
/* istanbul ignore if */
if (size < POINTER_OVERHEAD_IN_QUADS || size >= int32Array.length - FIRST_BLOCK_OFFSET_IN_QUADS) {
throw new Error(`Got invalid sized chunk at ${quadsToBytes(block)} (${quadsToBytes(size)} bytes).`);
}
else if (isFree(int32Array, block)) {
checkFreeBlockIntegrity(int32Array, block, size);
}
else {
checkUsedBlockIntegrity(int32Array, block, size);
}
block += size + POINTER_OVERHEAD_IN_QUADS;
}
return true;
}
function checkFreeBlockIntegrity (int32Array: Int32Array, block: int32, blockSize: int32): boolean {
/* istanbul ignore if */
if (int32Array[block - 1] !== int32Array[block + blockSize]) {
throw new Error(`Block length header does not match footer (${quadsToBytes(int32Array[block - 1])} vs ${quadsToBytes(int32Array[block + blockSize])}).`);
}
const height: int32 = int32Array[block + HEIGHT_OFFSET_IN_QUADS];
/* istanbul ignore if */
if (height < 1 || height > MAX_HEIGHT) {
throw new Error(`Block ${quadsToBytes(block)} height must be between 1 and ${MAX_HEIGHT}, got ${height}.`);
}
for (let i = 0; i < height; i++) {
const pointer = int32Array[block + NEXT_OFFSET_IN_QUADS + i];
/* istanbul ignore if */
if (pointer >= FIRST_BLOCK_OFFSET_IN_QUADS && !isFree(int32Array, pointer)) {
throw new Error(`Block ${quadsToBytes(block)} has a pointer to a non-free block (${quadsToBytes(pointer)}).`);
}
}
return true;
}
function checkUsedBlockIntegrity (int32Array: Int32Array, block: int32, blockSize: int32): boolean {
/* istanbul ignore if */
if (int32Array[block - 1] !== int32Array[block + blockSize]) {
throw new Error(`Block length header does not match footer (${quadsToBytes(int32Array[block - 1])} vs ${quadsToBytes(int32Array[block + blockSize])}).`);
}
else {
return true;
}
}
/**
* Inspect the freelist in the given array.
*/
export function inspect (int32Array: Int32Array): InspectionResult {
const blocks: {type: string; size: int32; node?: ListNode}[] = [];
const header: ListNode = readListNode(int32Array, HEADER_OFFSET_IN_QUADS);
let block: int32 = FIRST_BLOCK_OFFSET_IN_QUADS;
while (block < int32Array.length - POINTER_SIZE_IN_QUADS) {
const size: int32 = readSize(int32Array, block);
/* istanbul ignore if */
if (size < POINTER_OVERHEAD_IN_QUADS || size >= int32Array.length) {
throw new Error(`Got invalid sized chunk at ${quadsToBytes(block)} (${quadsToBytes(size)})`);
}
if (isFree(int32Array, block)) {
// @flowIssue todo
blocks.push(readListNode(int32Array, block));
}
else {
blocks.push({
type: 'used',
offset: quadsToBytes(block),
size: quadsToBytes(size)
});
}
block += size + POINTER_OVERHEAD_IN_QUADS;
}
return {header, blocks};
}
/**
* Convert quads to bytes.
*/
function quadsToBytes (num: int32): int32 {
return num * POINTER_SIZE_IN_BYTES;
}
/**
* Convert bytes to quads.
*/
function bytesToQuads (num: int32): int32 {
return Math.ceil(num / POINTER_SIZE_IN_BYTES);
}
/**
* Align the given value to 8 bytes.
*/
function align (value: int32): int32 {
return (value + ALIGNMENT_MASK) & ~ALIGNMENT_MASK;
}
/**
* Read the list pointers for a given block.
*/
function readListNode (int32Array: Int32Array, block: int32): ListNode {
pre: {
block + MIN_FREEABLE_SIZE_IN_QUADS < int32Array.length;
}
const height: int32 = int32Array[block + HEIGHT_OFFSET_IN_QUADS];
const pointers: int32[] = [];
for (let i = 0; i < height; i++) {
pointers.push(quadsToBytes(int32Array[block + NEXT_OFFSET_IN_QUADS + i]));
}
return {
type: 'free',
offset: quadsToBytes(block),
height,
pointers,
size: quadsToBytes(int32Array[block - 1])
};
}
/**
* Read the size (in quads) of the block at the given address.
*/
function readSize (int32Array: Int32Array, block: int32): int32 {
pre: {
block >= 1;
block < int32Array.length;
}
post: {
it > 0;
it <= int32Array.length;
int32Array[block - 1] === int32Array[block + Math.abs(int32Array[block - 1])];
}
return Math.abs(int32Array[block - 1]);
}
/**
* Write the size of the block at the given address.
* Note: This ONLY works for free blocks, not blocks in use.
*/
function writeFreeBlockSize (int32Array: Int32Array, size: int32, block: int32): void {
pre: {
block >= 1;
size !== 0;
}
post: {
int32Array[block - 1] === size;
int32Array[block + size] === size;
}
int32Array[block - 1] = size;
int32Array[block + size] = size;
}
/**
* Populate the `UPDATES` array with the offset of the last item in each
* list level, *before* a node of at least the given size.
*/
function findPredecessors (int32Array: Int32Array, minimumSize: int32): void {
pre: {
minimumSize >= MIN_FREEABLE_SIZE_IN_QUADS, "Cannot handle blocks smaller than the minimum freeable size.";
minimumSize < int32Array.length, "Cannot handle blocks larger than the capacity of the backing array.";
}
const listHeight: int32 = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
let node: int32 = HEADER_OFFSET_IN_QUADS;
for (let height = listHeight; height > 0; height--) {
let next: int32 = node + NEXT_OFFSET_IN_QUADS + (height - 1);
while (int32Array[next] >= FIRST_BLOCK_OFFSET_IN_QUADS && int32Array[int32Array[next] - 1] < minimumSize) {
node = int32Array[next];
next = node + NEXT_OFFSET_IN_QUADS + (height - 1);
}
UPDATES[height - 1] = node;
}
}
/**
* Find a free block with at least the given size and return its offset in quads.
*/
function findFreeBlock (int32Array: Int32Array, minimumSize: int32): int32 {
pre: {
minimumSize >= MIN_FREEABLE_SIZE_IN_QUADS;
minimumSize < int32Array.length;
}
post: {
it >= HEADER_OFFSET_IN_QUADS;
it < int32Array.length;
if (it !== HEADER_OFFSET_IN_QUADS) {
readSize(int32Array, it) >= minimumSize;
}
}
trace: `Finding a free block of at least ${quadsToBytes(minimumSize)} bytes.`;
let block: int32 = HEADER_OFFSET_IN_QUADS;
for (let height = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS]; height > 0; height--) {
let next: int32 = int32Array[block + NEXT_OFFSET_IN_QUADS + (height - 1)];
while (next !== HEADER_OFFSET_IN_QUADS && int32Array[next - 1] < minimumSize) {
block = next;
next = int32Array[block + NEXT_OFFSET_IN_QUADS + (height - 1)];
}
}
block = int32Array[block + NEXT_OFFSET_IN_QUADS];
if (block === HEADER_OFFSET_IN_QUADS) {
trace: `Could not find a block large enough.`;
return block;
}
else {
trace: `Got block ${quadsToBytes(block)} (${quadsToBytes(int32Array[block - 1])} bytes).`
return block;
}
}
/**
* Split the given block after a certain number of bytes and add the second half to the freelist.
*/
function split (int32Array: Int32Array, block: int32, firstSize: int32, blockSize: int32): void {
pre: {
block < int32Array.length;
firstSize >= MIN_FREEABLE_SIZE_IN_QUADS;
block + firstSize <= int32Array.length;
blockSize > firstSize;
block + blockSize <= int32Array.length;
}
const second: int32 = (block + firstSize + POINTER_OVERHEAD_IN_QUADS);
const secondSize: int32 = (blockSize - (second - block));
assert: {
secondSize >= MIN_FREEABLE_SIZE_IN_QUADS;
firstSize + secondSize + POINTER_OVERHEAD_IN_QUADS === blockSize;
}
trace: `Splitting block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes) into ${quadsToBytes(firstSize)} bytes and ${quadsToBytes(secondSize)} bytes.`;
remove(int32Array, block, blockSize);
assert: !hasPointersTo(int32Array, block), `All traces of the node must be removed.`;
int32Array[block - 1] = -firstSize;
int32Array[block + firstSize] = -firstSize;
assert: {
!isFree(int32Array, block);
}
trace: "Removed first block, inserting second.";
int32Array[second - 1] = -secondSize;
int32Array[second + secondSize] = -secondSize;
insert(int32Array, second, secondSize);
assert: isFree(int32Array, second);
}
/**
* Remove the given block from the freelist and mark it as allocated.
*/
function remove (int32Array: Int32Array, block: int32, blockSize: int32): void {
pre: {
block !== HEADER_OFFSET_IN_QUADS, "Cannot remove the header block.";
block < int32Array.length, "Block must be within bounds.";
blockSize >= MIN_FREEABLE_SIZE_IN_QUADS, "Block size must be at least the minimum freeable size.";
block + blockSize <= int32Array.length, "Block cannot exceed the length of the backing array.";
}
post: {
int32Array[block - 1] === -blockSize, "Block is marked as allocated.";
int32Array[block + blockSize] === -blockSize, "Block is marked as allocated.";
!hasPointersTo(int32Array, block), `All traces of the block (${quadsToBytes(block)}) must be removed. ${UPDATES.map(quadsToBytes).join(', ')}`;
}
trace: `Removing block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes).`;
findPredecessors(int32Array, blockSize);
let node: int32 = int32Array[UPDATES[0] + NEXT_OFFSET_IN_QUADS];
while (node !== block && node !== HEADER_OFFSET_IN_QUADS && int32Array[node - 1] <= blockSize) {
trace: `Skipping ${quadsToBytes(node)}`;
for (let height: number = int32Array[node + HEIGHT_OFFSET_IN_QUADS] - 1; height >= 0; height--) {
if (int32Array[node + NEXT_OFFSET_IN_QUADS + height] === block) {
UPDATES[height] = node;
}
}
node = int32Array[node + NEXT_OFFSET_IN_QUADS];
}
/* istanbul ignore if */
if (node !== block) {
throw new Error(`Could not find block to remove.`);
}
let listHeight: int32 = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
for (let height = 0; height < listHeight; height++) {
const next: int32 = int32Array[UPDATES[height] + NEXT_OFFSET_IN_QUADS + height];
if (next !== block) {
trace: `No higher level points to this node, so breaking early.`;
break;
}
int32Array[UPDATES[height] + NEXT_OFFSET_IN_QUADS + height] = int32Array[block + NEXT_OFFSET_IN_QUADS + height];
}
while (listHeight > 0 && int32Array[HEADER_OFFSET_IN_QUADS + NEXT_OFFSET_IN_QUADS + (listHeight - 1)] === HEADER_OFFSET_IN_QUADS) {
listHeight--;
int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS] = listHeight;
trace: `Reducing list height to ${listHeight}`;
}
// invert the size sign to signify an allocated block
int32Array[block - 1] = -blockSize;
int32Array[block + blockSize] = -blockSize;
}
/**
* Iterate all of the free blocks in the list, looking for pointers to the given block.
*/
function hasPointersTo (int32Array: Int32Array, block: int32): boolean {
let next: int32 = FIRST_BLOCK_OFFSET_IN_QUADS;
while (next < int32Array.length - POINTER_SIZE_IN_QUADS) {
if (isFree(int32Array, next)) {
for (let height = int32Array[next + HEIGHT_OFFSET_IN_QUADS] - 1; height >= 0; height--) {
const pointer: int32 = int32Array[next + NEXT_OFFSET_IN_QUADS + height];
/* istanbul ignore if */
if (pointer === block) {
return true;
}
}
}
next += readSize(int32Array, next) + POINTER_OVERHEAD_IN_QUADS;
}
return false;
}
/**
* Determine whether the block at the given address is free or not.
*/
function isFree (int32Array: Int32Array, block: int32): boolean {
pre: {
block < int32Array.length;
}
/* istanbul ignore if */
if (block < HEADER_SIZE_IN_QUADS) {
return false;
}
const size: int32 = int32Array[block - POINTER_SIZE_IN_QUADS];
assert: {
size !== 0;
if (size > 0) {
size >= MIN_FREEABLE_SIZE_IN_QUADS;
size < int32Array.length;
int32Array[block + size] === size;
}
else {
-size >= MIN_FREEABLE_SIZE_IN_QUADS;
-size < int32Array.length;
int32Array[block + -size] === size;
}
}
if (size < 0) {
return false;
}
else {
return true;
}
}
/**
* Get the address of the block before the given one and return the address *if it is free*,
* otherwise 0.
*/
function getFreeBlockBefore (int32Array: Int32Array, block: int32): int32 {
pre: {
block < int32Array.length;
}
post: {
it >= 0;
it < block;
}
if (block <= FIRST_BLOCK_OFFSET_IN_QUADS) {
return 0;
}
const beforeSize: int32 = int32Array[block - POINTER_OVERHEAD_IN_QUADS];
assert: {
beforeSize < int32Array.length;
}
if (beforeSize < POINTER_OVERHEAD_IN_QUADS) {
return 0;
}
return block - (POINTER_OVERHEAD_IN_QUADS + beforeSize);
}
/**
* Get the address of the block after the given one and return its address *if it is free*,
* otherwise 0.
*/
function getFreeBlockAfter (int32Array: Int32Array, block: int32): int32 {
pre: {
block < int32Array.length;
}
post: {
if (it !== 0) {
it > block;
it < int32Array.length - MIN_FREEABLE_SIZE_IN_QUADS;
}
}
const blockSize: int32 = readSize(int32Array, block);
if (block + blockSize + POINTER_OVERHEAD_IN_QUADS >= int32Array.length - 2) {
// Block is the last in the list.
return 0;
}
const next: int32 = (block + blockSize + POINTER_OVERHEAD_IN_QUADS);
const nextSize: int32 = int32Array[next - POINTER_SIZE_IN_QUADS];
assert: {
if (nextSize > 0) {
nextSize >= MIN_FREEABLE_SIZE_IN_QUADS;
next + nextSize <= int32Array.length;
}
}
if (nextSize < POINTER_OVERHEAD_IN_QUADS) {
return 0;
}
return next;
}
/**
* Insert the given block into the freelist and return the number of bytes that were freed.
*/
function insert (int32Array: Int32Array, block: int32, blockSize: int32): int32 {
pre: {
block < int32Array.length;
blockSize >= MIN_FREEABLE_SIZE_IN_QUADS;
block + blockSize <= int32Array.length;
!isFree(int32Array, block);
}
post: {
isFree(int32Array, block);
}
trace: `Inserting block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes).`;
findPredecessors(int32Array, blockSize);
const blockHeight: int32 = generateHeight(int32Array, block, blockSize);
const listHeight: int32 = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
for (let height = 1; height <= blockHeight; height++) {
assert: UPDATES[height - 1] > 0;
const update: int32 = UPDATES[height - 1] + NEXT_OFFSET_IN_QUADS + (height - 1);
trace: `Writing next (${height}) pointer (${quadsToBytes(UPDATES[height])}) to ${quadsToBytes(block)}.`;
int32Array[block + NEXT_OFFSET_IN_QUADS + (height - 1)] = int32Array[update];
int32Array[update] = block;
UPDATES[height - 1] = HEADER_OFFSET_IN_QUADS;
}
int32Array[block - 1] = blockSize;
int32Array[block + blockSize] = blockSize;
return blockSize;
}
/**
* Insert the given block into the freelist before the given free block,
* joining them together, returning the number of bytes which were freed.
*/
function insertBefore (int32Array: Int32Array, trailing: int32, block: int32, blockSize: int32): int32 {
pre: {
block > 0;
trailing > block;
trailing < int32Array.length;
}
post: {
it > 0;
it < int32Array.length;
}
const trailingSize: int32 = readSize(int32Array, trailing);
trace: `Inserting block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes) before block ${quadsToBytes(trailing)} (${quadsToBytes(trailingSize)} bytes).`;
remove(int32Array, trailing, trailingSize);
const size: int32 = (blockSize + trailingSize + POINTER_OVERHEAD_IN_QUADS);
int32Array[block - POINTER_SIZE_IN_QUADS] = -size;
int32Array[trailing + trailingSize] = -size;
insert(int32Array, block, size);
return blockSize;
}
/**
* Insert the given block into the freelist in between the given free blocks,
* joining them together, returning the number of bytes which were freed.
*/
function insertMiddle (int32Array: Int32Array, preceding: int32, block: int32, blockSize: int32, trailing: int32): int32 {
pre: {
block > 0;
preceding < block;
trailing > block;
trailing < int32Array.length;
}
post: {
it > 0;
it < int32Array.length;
}
const precedingSize: int32 = readSize(int32Array, preceding);
const trailingSize: int32 = readSize(int32Array, trailing);
const size: int32 = ((trailing - preceding) + trailingSize);
trace: `Inserting block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes) between blocks ${quadsToBytes(preceding)} (${quadsToBytes(precedingSize)} bytes) and ${quadsToBytes(trailing)} (${quadsToBytes(trailingSize)} bytes).`;
remove(int32Array, preceding, precedingSize);
remove(int32Array, trailing, trailingSize);
int32Array[preceding - POINTER_SIZE_IN_QUADS] = -size;
int32Array[trailing + trailingSize] = -size;
insert(int32Array, preceding, size);
return blockSize;
}
/**
* Insert the given block into the freelist after the given free block,
* joining them together, returning the number of bytes which were freed.
*/
function insertAfter (int32Array: Int32Array, preceding: int32, block: int32, blockSize: int32): int32 {
pre: {
block > 0;
preceding < block;
block < int32Array.length;
}
post: {
it > 0;
it < int32Array.length;
}
const precedingSize: int32 = (block - preceding) - POINTER_OVERHEAD_IN_QUADS;
trace: `Inserting block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes) after block ${quadsToBytes(preceding)} (${quadsToBytes(precedingSize)} bytes).`;
const size: int32 = ((block - preceding) + blockSize);
remove(int32Array, preceding, precedingSize);
int32Array[preceding - POINTER_SIZE_IN_QUADS] = -size;
int32Array[block + blockSize] = -size;
insert(int32Array, preceding, size);
return blockSize;
}
/**
* Generate a random height for a block, growing the list height by 1 if required.
*/
function generateHeight (int32Array: Int32Array, block: int32, blockSize: int32): int32 {
pre: {
blockSize >= MIN_FREEABLE_SIZE_IN_QUADS;
blockSize < int32Array.length;
}
post: {
it > 0;
it <= MAX_HEIGHT;
Math.floor(it) === it;
}
const listHeight: int32 = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
let height: int32 = randomHeight();
trace: `Generating a block height for block ${quadsToBytes(block)} (${quadsToBytes(blockSize)} bytes, ${blockSize} quads), got ${height}.`;
if (blockSize - 1 < height + 1) {
height = blockSize - 2;
trace: `Block size is too small for the generated height, reducing height to ${height}.`;
}
if (height > listHeight) {
const newHeight: int32 = listHeight + 1;
trace: `Increasing list height from ${listHeight} to ${newHeight}.`;
int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS] = newHeight;
int32Array[HEADER_OFFSET_IN_QUADS + NEXT_OFFSET_IN_QUADS + (newHeight - 1)] = HEADER_OFFSET_IN_QUADS;
UPDATES[newHeight] = HEADER_OFFSET_IN_QUADS;
int32Array[block + HEIGHT_OFFSET_IN_QUADS] = newHeight;
return newHeight;
}
else {
int32Array[block + HEIGHT_OFFSET_IN_QUADS] = height;
return height;
}
}
/**
* Generate a random height for a new block.
*/
function randomHeight (): number {
post: {
it > 0;
it <= MAX_HEIGHT;
Math.floor(it) === it;
}
let height: number = 1;
for (let r: number = Math.ceil(Math.random() * 2147483648); (r & 1) === 1 && height < MAX_HEIGHT; r >>= 1) {
height++;
Math.ceil(Math.random() * 2147483648)
}
return height;
}