malloc
Version:
Simple malloc() & free() implementation on top of buffers and array buffers.
668 lines (552 loc) • 21.4 kB
JavaScript
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.prepare = prepare;
exports.verifyHeader = verifyHeader;
exports.checkListIntegrity = checkListIntegrity;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var POINTER_SIZE_IN_BYTES = 4;
var MAX_HEIGHT = 32;
var HEADER_SIZE_IN_QUADS = 1 + MAX_HEIGHT * 2;
var HEADER_OFFSET_IN_QUADS = 1;
var HEIGHT_OFFSET_IN_QUADS = 0;
var PREV_OFFSET_IN_QUADS = 1;
var NEXT_OFFSET_IN_QUADS = 2;
var POINTER_SIZE_IN_QUADS = 1;
var POINTER_OVERHEAD_IN_QUADS = 2;
var MIN_FREEABLE_SIZE_IN_QUADS = 3;
var FIRST_BLOCK_OFFSET_IN_QUADS = HEADER_OFFSET_IN_QUADS + HEADER_SIZE_IN_QUADS + POINTER_OVERHEAD_IN_QUADS;
var MIN_FREEABLE_SIZE_IN_BYTES = 16;
var FIRST_BLOCK_OFFSET_IN_BYTES = FIRST_BLOCK_OFFSET_IN_QUADS * POINTER_SIZE_IN_BYTES;
var OVERHEAD_IN_BYTES = (FIRST_BLOCK_OFFSET_IN_QUADS + 1) * POINTER_SIZE_IN_BYTES;
var ALIGNMENT_IN_BYTES = 8;
var ALIGNMENT_MASK = ALIGNMENT_IN_BYTES - 1;
var UPDATES = new Int32Array(MAX_HEIGHT).fill(HEADER_OFFSET_IN_QUADS);
var Allocator = function () {
/**
* Initialize the allocator from the given Buffer or ArrayBuffer.
*/
function Allocator(buffer) {
var byteOffset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
var byteLength = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
_classCallCheck(this, Allocator);
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');
}
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.
*/
_createClass(Allocator, [{
key: 'alloc',
value: function alloc(numberOfBytes) {
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');
}
var minimumSize = bytesToQuads(numberOfBytes);
var int32Array = this.int32Array;
var block = findFreeBlock(int32Array, minimumSize);
if (block <= HEADER_OFFSET_IN_QUADS) {
return 0;
}
var blockSize = readSize(int32Array, block);
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.
*/
}, {
key: 'calloc',
value: function calloc(numberOfBytes) {
if (numberOfBytes < MIN_FREEABLE_SIZE_IN_BYTES) {
numberOfBytes = MIN_FREEABLE_SIZE_IN_BYTES;
} else {
numberOfBytes = align(numberOfBytes);
}
var address = this.alloc(numberOfBytes);
if (address === 0) {
// Not enough space
return 0;
}
var int32Array = this.int32Array;
var offset = bytesToQuads(address);
var limit = numberOfBytes / 4;
for (var i = 0; i < limit; i++) {
int32Array[offset + i] = 0;
}
return address;
}
/**
* Free a number of bytes from the given address.
*/
}, {
key: 'free',
value: function free(address) {
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));
}
var int32Array = this.int32Array;
var block = bytesToQuads(address);
var blockSize = readSize(int32Array, block);
/* 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));
}
var preceding = getFreeBlockBefore(int32Array, block);
var trailing = 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.
*/
}, {
key: 'sizeOf',
value: function sizeOf(address) {
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.
*/
}, {
key: 'inspect',
value: function inspect() {
return _inspect(this.int32Array);
}
}]);
return Allocator;
}();
/**
* Prepare the given int32Array and ensure it contains a valid header.
*/
exports.default = Allocator;
function prepare(int32Array) {
if (!verifyHeader(int32Array)) {
writeInitialHeader(int32Array);
}
return int32Array;
}
/**
* Verify that the int32Array contains a valid header.
*/
function verifyHeader(int32Array) {
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) {
var header = HEADER_OFFSET_IN_QUADS;
var headerSize = HEADER_SIZE_IN_QUADS;
var block = FIRST_BLOCK_OFFSET_IN_QUADS;
var 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 (var _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.
*/
function checkListIntegrity(int32Array) {
var block = FIRST_BLOCK_OFFSET_IN_QUADS;
while (block < int32Array.length - POINTER_SIZE_IN_QUADS) {
var _size = 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, block, blockSize) {
/* 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]) + ').');
}
var height = 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 (var i = 0; i < height; i++) {
var 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, block, blockSize) {
/* 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.
*/
function _inspect(int32Array) {
var blocks = [];
var header = readListNode(int32Array, HEADER_OFFSET_IN_QUADS);
var block = FIRST_BLOCK_OFFSET_IN_QUADS;
while (block < int32Array.length - POINTER_SIZE_IN_QUADS) {
var _size2 = readSize(int32Array, block);
/* istanbul ignore if */
if (_size2 < POINTER_OVERHEAD_IN_QUADS || _size2 >= int32Array.length) {
throw new Error('Got invalid sized chunk at ' + quadsToBytes(block) + ' (' + quadsToBytes(_size2) + ')');
}
if (isFree(int32Array, block)) {
// Issue todo
blocks.push(readListNode(int32Array, block));
} else {
blocks.push({
type: 'used',
offset: quadsToBytes(block),
size: quadsToBytes(_size2)
});
}
block += _size2 + POINTER_OVERHEAD_IN_QUADS;
}
return { header: header, blocks: blocks };
}
/**
* Convert quads to bytes.
*/
exports.inspect = _inspect;
function quadsToBytes(num) {
return num * POINTER_SIZE_IN_BYTES;
}
/**
* Convert bytes to quads.
*/
function bytesToQuads(num) {
return Math.ceil(num / POINTER_SIZE_IN_BYTES);
}
/**
* Align the given value to 8 bytes.
*/
function align(value) {
return value + ALIGNMENT_MASK & ~ALIGNMENT_MASK;
}
/**
* Read the list pointers for a given block.
*/
function readListNode(int32Array, block) {
var height = int32Array[block + HEIGHT_OFFSET_IN_QUADS];
var pointers = [];
for (var i = 0; i < height; i++) {
pointers.push(quadsToBytes(int32Array[block + NEXT_OFFSET_IN_QUADS + i]));
}
return {
type: 'free',
offset: quadsToBytes(block),
height: height,
pointers: pointers,
size: quadsToBytes(int32Array[block - 1])
};
}
/**
* Read the size (in quads) of the block at the given address.
*/
function readSize(int32Array, block) {
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, size, block) {
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, minimumSize) {
var listHeight = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
var node = HEADER_OFFSET_IN_QUADS;
for (var _height2 = listHeight; _height2 > 0; _height2--) {
var next = node + NEXT_OFFSET_IN_QUADS + (_height2 - 1);
while (int32Array[next] >= FIRST_BLOCK_OFFSET_IN_QUADS && int32Array[int32Array[next] - 1] < minimumSize) {
node = int32Array[next];
next = node + NEXT_OFFSET_IN_QUADS + (_height2 - 1);
}
UPDATES[_height2 - 1] = node;
}
}
/**
* Find a free block with at least the given size and return its offset in quads.
*/
function findFreeBlock(int32Array, minimumSize) {
var block = HEADER_OFFSET_IN_QUADS;
for (var _height3 = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS]; _height3 > 0; _height3--) {
var next = int32Array[block + NEXT_OFFSET_IN_QUADS + (_height3 - 1)];
while (next !== HEADER_OFFSET_IN_QUADS && int32Array[next - 1] < minimumSize) {
block = next;
next = int32Array[block + NEXT_OFFSET_IN_QUADS + (_height3 - 1)];
}
}
block = int32Array[block + NEXT_OFFSET_IN_QUADS];
if (block === HEADER_OFFSET_IN_QUADS) {
return block;
} else {
return block;
}
}
/**
* Split the given block after a certain number of bytes and add the second half to the freelist.
*/
function split(int32Array, block, firstSize, blockSize) {
var second = block + firstSize + POINTER_OVERHEAD_IN_QUADS;
var secondSize = blockSize - (second - block);
remove(int32Array, block, blockSize);
int32Array[block - 1] = -firstSize;
int32Array[block + firstSize] = -firstSize;
int32Array[second - 1] = -secondSize;
int32Array[second + secondSize] = -secondSize;
insert(int32Array, second, secondSize);
}
/**
* Remove the given block from the freelist and mark it as allocated.
*/
function remove(int32Array, block, blockSize) {
findPredecessors(int32Array, blockSize);
var node = int32Array[UPDATES[0] + NEXT_OFFSET_IN_QUADS];
while (node !== block && node !== HEADER_OFFSET_IN_QUADS && int32Array[node - 1] <= blockSize) {
for (var _height4 = int32Array[node + HEIGHT_OFFSET_IN_QUADS] - 1; _height4 >= 0; _height4--) {
if (int32Array[node + NEXT_OFFSET_IN_QUADS + _height4] === block) {
UPDATES[_height4] = node;
}
}
node = int32Array[node + NEXT_OFFSET_IN_QUADS];
}
/* istanbul ignore if */
if (node !== block) {
throw new Error('Could not find block to remove.');
}
var listHeight = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
for (var _height5 = 0; _height5 < listHeight; _height5++) {
var next = int32Array[UPDATES[_height5] + NEXT_OFFSET_IN_QUADS + _height5];
if (next !== block) {
break;
}
int32Array[UPDATES[_height5] + NEXT_OFFSET_IN_QUADS + _height5] = int32Array[block + NEXT_OFFSET_IN_QUADS + _height5];
}
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;
}
// 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, block) {
var next = FIRST_BLOCK_OFFSET_IN_QUADS;
while (next < int32Array.length - POINTER_SIZE_IN_QUADS) {
if (isFree(int32Array, next)) {
for (var _height6 = int32Array[next + HEIGHT_OFFSET_IN_QUADS] - 1; _height6 >= 0; _height6--) {
var pointer = int32Array[next + NEXT_OFFSET_IN_QUADS + _height6];
/* 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, block) {
/* istanbul ignore if */
if (block < HEADER_SIZE_IN_QUADS) {
return false;
}
var size = int32Array[block - POINTER_SIZE_IN_QUADS];
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, block) {
if (block <= FIRST_BLOCK_OFFSET_IN_QUADS) {
return 0;
}
var beforeSize = int32Array[block - POINTER_OVERHEAD_IN_QUADS];
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, block) {
var blockSize = readSize(int32Array, block);
if (block + blockSize + POINTER_OVERHEAD_IN_QUADS >= int32Array.length - 2) {
// Block is the last in the list.
return 0;
}
var next = block + blockSize + POINTER_OVERHEAD_IN_QUADS;
var nextSize = int32Array[next - POINTER_SIZE_IN_QUADS];
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, block, blockSize) {
findPredecessors(int32Array, blockSize);
var blockHeight = generateHeight(int32Array, block, blockSize);
var listHeight = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
for (var _height7 = 1; _height7 <= blockHeight; _height7++) {
var update = UPDATES[_height7 - 1] + NEXT_OFFSET_IN_QUADS + (_height7 - 1);
int32Array[block + NEXT_OFFSET_IN_QUADS + (_height7 - 1)] = int32Array[update];
int32Array[update] = block;
UPDATES[_height7 - 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, trailing, block, blockSize) {
var trailingSize = readSize(int32Array, trailing);
remove(int32Array, trailing, trailingSize);
var size = 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, preceding, block, blockSize, trailing) {
var precedingSize = readSize(int32Array, preceding);
var trailingSize = readSize(int32Array, trailing);
var size = trailing - preceding + trailingSize;
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, preceding, block, blockSize) {
var precedingSize = block - preceding - POINTER_OVERHEAD_IN_QUADS;
var size = 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, block, blockSize) {
var listHeight = int32Array[HEADER_OFFSET_IN_QUADS + HEIGHT_OFFSET_IN_QUADS];
var height = randomHeight();
if (blockSize - 1 < height + 1) {
height = blockSize - 2;
}
if (height > listHeight) {
var newHeight = listHeight + 1;
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() {
var height = 1;
for (var r = Math.ceil(Math.random() * 2147483648); (r & 1) === 1 && height < MAX_HEIGHT; r >>= 1) {
height++;
Math.ceil(Math.random() * 2147483648);
}
return height;
}