mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-26 15:32:16 +00:00
Refactor TLSF to a more TS-friendly structure
This commit is contained in:
parent
7d5e56cef5
commit
1662950f3c
@ -1,4 +1,6 @@
|
|||||||
// based on https://github.com/mattconte/tlsf (BSD, see LICENSE file)
|
////////////// TLSF (Two-Level Segregate Fit) Memory Allocator ////////////////
|
||||||
|
// based on https://github.com/mattconte/tlsf - BSD, see LICENSE file //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
|
||||||
@ -27,16 +29,151 @@ function fls<T>(word: T): i32 {
|
|||||||
/** Block header structure. */
|
/** Block header structure. */
|
||||||
@explicit
|
@explicit
|
||||||
class BlockHeader {
|
class BlockHeader {
|
||||||
/** Points to the previous physical block. Only valid if the previous block
|
|
||||||
* is free. Actually stored at the end of the previous block. */
|
///////////////////////////////// Fields ////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points to the previous physical block. Only valid if the previous block is
|
||||||
|
* free. Actually stored at the end of the previous block.
|
||||||
|
*/
|
||||||
prev_phys_block: BlockHeader;
|
prev_phys_block: BlockHeader;
|
||||||
/** The size of this block, excluding the block header. The two least
|
|
||||||
* significant bits are used to store the block status. */
|
/**
|
||||||
|
* The size of this block, excluding the block header. The two least
|
||||||
|
* significant bits are used to store the block status.
|
||||||
|
*/
|
||||||
tagged_size: usize;
|
tagged_size: usize;
|
||||||
|
|
||||||
/** Next free block. Only valid if the block is free. */
|
/** Next free block. Only valid if the block is free. */
|
||||||
next_free: BlockHeader;
|
next_free: BlockHeader;
|
||||||
|
|
||||||
/** Previous free block. Only valid if the block is free. */
|
/** Previous free block. Only valid if the block is free. */
|
||||||
prev_free: BlockHeader;
|
prev_free: BlockHeader;
|
||||||
|
|
||||||
|
///////////////////////////////// Methods ///////////////////////////////////
|
||||||
|
|
||||||
|
/** Gets the size of this block, excluding the block header. */
|
||||||
|
get size(): usize {
|
||||||
|
return this.tagged_size & ~(block_header_free_bit | block_header_prev_free_bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the size of this block, retaining tagged bits. */
|
||||||
|
set size(size: usize) {
|
||||||
|
this.tagged_size = size | (this.tagged_size & (block_header_free_bit | block_header_prev_free_bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if this is the last block. */
|
||||||
|
get isLast(): bool {
|
||||||
|
return this.size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if this block's status is 'free'. */
|
||||||
|
get isFree(): bool {
|
||||||
|
return (this.tagged_size & block_header_free_bit) == block_header_free_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets this block's status to 'free'. */
|
||||||
|
setFree(): void {
|
||||||
|
this.tagged_size |= block_header_free_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets this block's status to 'used'. */
|
||||||
|
setUsed(): void {
|
||||||
|
this.tagged_size &= ~block_header_free_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if the previous block is free. */
|
||||||
|
get isPrevFree(): bool {
|
||||||
|
return (this.tagged_size & block_header_prev_free_bit) == block_header_prev_free_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the previous block's status to 'free'. */
|
||||||
|
setPrevFree(): void {
|
||||||
|
this.tagged_size |= block_header_prev_free_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the previous block's status to 'used'. */
|
||||||
|
setPrevUsed(): void {
|
||||||
|
this.tagged_size &= ~block_header_prev_free_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the block header matching the specified payload pointer. */
|
||||||
|
static fromPayloadPtr(ptr: usize): BlockHeader {
|
||||||
|
return changetype<BlockHeader>(ptr - block_start_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the address of this block's payload. */
|
||||||
|
toPayloadPtr(): usize {
|
||||||
|
return changetype<usize>(this) + block_start_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the next block after this one using the specified size. */
|
||||||
|
static fromOffset(ptr: usize, size: usize): BlockHeader {
|
||||||
|
return changetype<BlockHeader>(ptr + <usize>size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the previous block. */
|
||||||
|
get prev(): BlockHeader {
|
||||||
|
assert(this.isPrevFree, "previous block must be free");
|
||||||
|
return this.prev_phys_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the next block. */
|
||||||
|
get next(): BlockHeader {
|
||||||
|
assert(!this.isLast, "last block has no next block");
|
||||||
|
return BlockHeader.fromOffset(this.toPayloadPtr(), this.size - block_header_overhead);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links this block with its physical next block and returns the next block.
|
||||||
|
*/
|
||||||
|
linkNext(): BlockHeader {
|
||||||
|
var next = this.next;
|
||||||
|
next.prev_phys_block = this;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marks this block as being 'free'. */
|
||||||
|
markAsFree(): void {
|
||||||
|
var next = this.linkNext(); // Link the block to the next block, first.
|
||||||
|
next.setPrevFree();
|
||||||
|
this.setFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marks this block as being 'used'. */
|
||||||
|
markAsUsed(): void {
|
||||||
|
var next = this.next;
|
||||||
|
next.setPrevUsed();
|
||||||
|
this.setUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if this block can be splitted. */
|
||||||
|
canSplit(size: usize): bool {
|
||||||
|
return this.size >= sizeof_block_header_t + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splits a block into two, the second of which is free. */
|
||||||
|
split(size: usize): BlockHeader {
|
||||||
|
// Calculate the amount of space left in the remaining block.
|
||||||
|
var remaining = BlockHeader.fromOffset(this.toPayloadPtr(), size - block_header_overhead);
|
||||||
|
var remain_size = this.size - (size + block_header_overhead);
|
||||||
|
assert(remaining.toPayloadPtr() == align_ptr(remaining.toPayloadPtr(), ALIGN_SIZE), "remaining block not aligned properly");
|
||||||
|
assert(this.size == remain_size + size + block_header_overhead);
|
||||||
|
|
||||||
|
remaining.size = remain_size;
|
||||||
|
assert(remaining.size >= block_size_min, "block split with invalid size");
|
||||||
|
|
||||||
|
this.size = size;
|
||||||
|
remaining.markAsFree();
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Absorb a free block's storage into this (adjacent previous) free block. */
|
||||||
|
absorb(block: BlockHeader): void {
|
||||||
|
assert(!this.isLast, "previous block can't be last");
|
||||||
|
this.tagged_size += block.size + block_header_overhead; // Leaves flags untouched.
|
||||||
|
this.linkNext();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeof_block_header_t: usize = 4 * sizeof<usize>();
|
const sizeof_block_header_t: usize = 4 * sizeof<usize>();
|
||||||
@ -62,111 +199,230 @@ const block_size_max: usize = <usize>1 << FL_INDEX_MAX;
|
|||||||
/* The TLSF control structure. */
|
/* The TLSF control structure. */
|
||||||
@explicit
|
@explicit
|
||||||
class Control extends BlockHeader { // Empty lists point at this block to indicate they are free.
|
class Control extends BlockHeader { // Empty lists point at this block to indicate they are free.
|
||||||
|
|
||||||
|
///////////////////////////////// Fields ////////////////////////////////////
|
||||||
|
|
||||||
/* First level free list bitmap. */
|
/* First level free list bitmap. */
|
||||||
fl_bitmap: u32;
|
fl_bitmap: u32;
|
||||||
/** Second level free list bitmaps. */
|
|
||||||
|
/** Gets the second level free list bitmap for the specified index. Equivalent to `sl_bitmap[fl_index]`. */
|
||||||
sl_bitmap(fl_index: u32): u32 {
|
sl_bitmap(fl_index: u32): u32 {
|
||||||
const offset: usize = sizeof_block_header_t + sizeof<u32>();
|
const offset: usize = sizeof_block_header_t + sizeof<u32>();
|
||||||
return load<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>());
|
return load<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the second level free list bitmap for the specified index. Equivalent to `sl_bitmap[fl_index] = sl_map`. */
|
||||||
sl_bitmap_set(fl_index: u32, sl_map: u32): void {
|
sl_bitmap_set(fl_index: u32, sl_map: u32): void {
|
||||||
const offset: usize = sizeof_block_header_t + sizeof<u32>();
|
const offset: usize = sizeof_block_header_t + sizeof<u32>();
|
||||||
return store<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>(), sl_map);
|
return store<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>(), sl_map);
|
||||||
}
|
}
|
||||||
/** Head of free lists. */
|
|
||||||
|
/** Gets the head of the free list for the specified indexes. Equivalent to `blocks[fl_index][sl_index]`. */
|
||||||
blocks(fl_index: u32, sl_index: u32): BlockHeader {
|
blocks(fl_index: u32, sl_index: u32): BlockHeader {
|
||||||
const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
|
const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
|
||||||
return load<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>());
|
return load<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the head of the free list for the specified indexes. Equivalent to `blocks[fl_index][sl_index] = block`. */
|
||||||
blocks_set(fl_index: u32, sl_index: u32, block: BlockHeader): void {
|
blocks_set(fl_index: u32, sl_index: u32, block: BlockHeader): void {
|
||||||
const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
|
const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
|
||||||
return store<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>(), block);
|
return store<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>(), block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////// Methods ///////////////////////////////////
|
||||||
|
|
||||||
|
/** Removes a given block from the free list. */
|
||||||
|
removeBlock(block: BlockHeader): void {
|
||||||
|
mapping_insert(block.size);
|
||||||
|
this.removeFreeBlock(block, fl_out, sl_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inserts a given block into the free list. */
|
||||||
|
insertBlock(block: BlockHeader): void {
|
||||||
|
mapping_insert(block.size);
|
||||||
|
this.insertFreeBlock(block, fl_out, sl_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inserts a free block into the free block list. */
|
||||||
|
insertFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
|
||||||
|
var current = this.blocks(fl, sl);
|
||||||
|
assert(current, "free list cannot have a null entry");
|
||||||
|
assert(block, "cannot insert a null entry into the free list");
|
||||||
|
block.next_free = current;
|
||||||
|
block.prev_free = this;
|
||||||
|
current.prev_free = block;
|
||||||
|
|
||||||
|
assert(block.toPayloadPtr() == align_ptr(block.toPayloadPtr(), ALIGN_SIZE), "block not aligned properly");
|
||||||
|
|
||||||
|
// Insert the new block at the head of the list, and mark the first-
|
||||||
|
// and second-level bitmaps appropriately.
|
||||||
|
this.blocks_set(fl, sl, block);
|
||||||
|
this.fl_bitmap |= (1 << fl);
|
||||||
|
this.sl_bitmap_set(fl, this.sl_bitmap(fl) | (1 << sl))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Removes a free block from the free list.*/
|
||||||
|
removeFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
|
||||||
|
var prev = block.prev_free;
|
||||||
|
var next = block.next_free;
|
||||||
|
assert(prev, "prev_free field can not be null");
|
||||||
|
assert(next, "next_free field can not be null");
|
||||||
|
next.prev_free = prev;
|
||||||
|
prev.next_free = next;
|
||||||
|
if (this.blocks(fl, sl) == block) {
|
||||||
|
this.blocks_set(fl, sl, next);
|
||||||
|
if (next == this) {
|
||||||
|
this.sl_bitmap_set(fl, this.sl_bitmap(fl) & ~(1 << sl));
|
||||||
|
if (!this.sl_bitmap(fl)) {
|
||||||
|
this.fl_bitmap &= ~(1 << fl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Merges a just-freed block with an adjacent previous free block. */
|
||||||
|
mergePrevBlock(block: BlockHeader): BlockHeader {
|
||||||
|
if (block.isPrevFree) {
|
||||||
|
var prev = block.prev;
|
||||||
|
assert(prev, "prev physical block can't be null");
|
||||||
|
assert(prev.isFree, "prev block is not free though marked as such");
|
||||||
|
this.removeBlock(prev);
|
||||||
|
prev.absorb(block);
|
||||||
|
block = prev;
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Merges a just-freed block with an adjacent free block. */
|
||||||
|
mergeNextBlock(block: BlockHeader): BlockHeader {
|
||||||
|
var next = block.next;
|
||||||
|
assert(next, "next physical block can't be null");
|
||||||
|
if (next.isFree) {
|
||||||
|
assert(!block.isLast, "previous block can't be last");
|
||||||
|
this.removeBlock(next);
|
||||||
|
block.absorb(next);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Trims any trailing block space off the end of a block and returns it to the pool. */
|
||||||
|
trimFreeBlock(block: BlockHeader, size: usize): void {
|
||||||
|
assert(block.isFree, "block must be free");
|
||||||
|
if (block.canSplit(size)) {
|
||||||
|
var remaining_block = block.split(size);
|
||||||
|
block.linkNext();
|
||||||
|
remaining_block.setPrevFree();
|
||||||
|
this.insertBlock(remaining_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Trims any trailing block space off the end of a used block and returns it to the pool. */
|
||||||
|
trimUsedBlock(block: BlockHeader, size: usize): void {
|
||||||
|
assert(!block.isFree, "block must be used");
|
||||||
|
if (block.canSplit(size)) {
|
||||||
|
// If the next block is free, we must coalesce.
|
||||||
|
var remaining_block = block.split(size);
|
||||||
|
remaining_block.setPrevUsed();
|
||||||
|
remaining_block = this.mergeNextBlock(remaining_block);
|
||||||
|
this.insertBlock(remaining_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimFreeBlockLeading(block: BlockHeader, size: usize): BlockHeader {
|
||||||
|
var remaining_block = block;
|
||||||
|
if (block.canSplit(size)) {
|
||||||
|
remaining_block = block.split(size - block_header_overhead);
|
||||||
|
remaining_block.setPrevFree();
|
||||||
|
block.linkNext();
|
||||||
|
this.insertBlock(block);
|
||||||
|
}
|
||||||
|
return remaining_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
locateFreeBlock(size: usize): BlockHeader {
|
||||||
|
var index: u64 = 0;
|
||||||
|
var block: BlockHeader = changetype<BlockHeader>(0);
|
||||||
|
if (size) {
|
||||||
|
mapping_search(size);
|
||||||
|
if (fl_out < FL_INDEX_MAX) {
|
||||||
|
block = find_suitable_block(this, fl_out, sl_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (block) {
|
||||||
|
assert(block.size >= size);
|
||||||
|
this.removeFreeBlock(block, fl_out, sl_out);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareUsedBlock(block: BlockHeader, size: usize): usize {
|
||||||
|
var ptr: usize = 0;
|
||||||
|
if (block) {
|
||||||
|
assert(size, "size must be non-zero");
|
||||||
|
this.trimFreeBlock(block, size);
|
||||||
|
block.markAsUsed();
|
||||||
|
ptr = block.toPayloadPtr();
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a TLSF control structure at the specified memory address, providing the specified number of bytes. */
|
||||||
|
static create(mem: usize, bytes: usize): Control {
|
||||||
|
if ((mem % ALIGN_SIZE) != 0)
|
||||||
|
throw new RangeError("Memory must be aligned");
|
||||||
|
|
||||||
|
// Clear structure and point all empty lists at the null block.
|
||||||
|
var control = changetype<Control>(mem);
|
||||||
|
control.next_free = control;
|
||||||
|
control.prev_free = control;
|
||||||
|
control.fl_bitmap = 0;
|
||||||
|
for (var i = 0; i < FL_INDEX_COUNT; ++i) {
|
||||||
|
control.sl_bitmap_set(i, 0);
|
||||||
|
for (var j = 0; j < SL_INDEX_COUNT; ++j) {
|
||||||
|
control.blocks_set(i, j, control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the initial memory pool
|
||||||
|
control.addPool(mem + sizeof_control_t, bytes - sizeof_control_t);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a pool of free memory. */
|
||||||
|
addPool(mem: usize, bytes: usize): void {
|
||||||
|
var block: BlockHeader;
|
||||||
|
var next: BlockHeader;
|
||||||
|
|
||||||
|
// Overhead of the TLSF structures in a given memory block, equal
|
||||||
|
// to the overhead of the free block and the sentinel block.
|
||||||
|
const pool_overhead: usize = 2 * block_header_overhead;
|
||||||
|
|
||||||
|
var pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
|
||||||
|
if ((mem % ALIGN_SIZE) != 0)
|
||||||
|
throw new RangeError("Memory must be aligned");
|
||||||
|
if (pool_bytes < block_size_min || pool_bytes > block_size_max)
|
||||||
|
throw new RangeError("Memory size must be between min and max");
|
||||||
|
|
||||||
|
// Create the main free block. Offset the start of the block slightly
|
||||||
|
// so that the prev_phys_block field falls outside of the pool -
|
||||||
|
// it will never be used.
|
||||||
|
block = BlockHeader.fromOffset(mem, -block_header_overhead);
|
||||||
|
block.size = pool_bytes;
|
||||||
|
block.setFree();
|
||||||
|
block.setPrevUsed();
|
||||||
|
this.insertBlock(block);
|
||||||
|
|
||||||
|
// Split the block to create a zero-size sentinel block.
|
||||||
|
next = block.linkNext();
|
||||||
|
next.size = 0;
|
||||||
|
next.setUsed();
|
||||||
|
next.setPrevFree();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof<u32>() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof<usize>();
|
const sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof<u32>() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof<usize>();
|
||||||
|
|
||||||
function block_size(block: BlockHeader): usize {
|
// Alignment helpers
|
||||||
return block.tagged_size & ~(block_header_free_bit | block_header_prev_free_bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_set_size(block: BlockHeader, size: usize): void {
|
|
||||||
block.tagged_size = size | (block.tagged_size & (block_header_free_bit | block_header_prev_free_bit));
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_is_last(block: BlockHeader): bool {
|
|
||||||
return block_size(block) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_is_free(block: BlockHeader): bool {
|
|
||||||
return (block.tagged_size & block_header_free_bit) == block_header_free_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_set_free(block: BlockHeader): void {
|
|
||||||
block.tagged_size |= block_header_free_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_set_used(block: BlockHeader): void {
|
|
||||||
block.tagged_size &= ~block_header_free_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_is_prev_free(block: BlockHeader): bool {
|
|
||||||
return (block.tagged_size & block_header_prev_free_bit) == block_header_prev_free_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_set_prev_free(block: BlockHeader): void {
|
|
||||||
block.tagged_size |= block_header_prev_free_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_set_prev_used(block: BlockHeader): void {
|
|
||||||
block.tagged_size &= ~block_header_prev_free_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_from_ptr(ptr: usize): BlockHeader {
|
|
||||||
return changetype<BlockHeader>(ptr - block_start_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_to_ptr(block: BlockHeader): usize {
|
|
||||||
return changetype<usize>(block) + block_start_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return location of next block after block of given size. */
|
|
||||||
function offset_to_block(ptr: usize, size: usize): BlockHeader {
|
|
||||||
return changetype<BlockHeader>(ptr + <usize>size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return location of previous block. */
|
|
||||||
function block_prev(block: BlockHeader): BlockHeader {
|
|
||||||
assert(block_is_prev_free(block), "previous block must be free");
|
|
||||||
return block.prev_phys_block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return location of next existing block. */
|
|
||||||
function block_next(block: BlockHeader): BlockHeader {
|
|
||||||
var next = offset_to_block(block_to_ptr(block), block_size(block) - block_header_overhead);
|
|
||||||
assert(!block_is_last(block), "last block has no next block");
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Link a new block with its physical neighbor, return the neighbor. */
|
|
||||||
function block_link_next(block: BlockHeader): BlockHeader {
|
|
||||||
var next = block_next(block);
|
|
||||||
next.prev_phys_block = block;
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_mark_as_free(block: BlockHeader): void {
|
|
||||||
// Link the block to the next block, first.
|
|
||||||
var next = block_link_next(block);
|
|
||||||
block_set_prev_free(next);
|
|
||||||
block_set_free(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_mark_as_used(block: BlockHeader): void {
|
|
||||||
var next = block_next(block);
|
|
||||||
block_set_prev_used(next);
|
|
||||||
block_set_used(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
function align_up(x: usize, align: usize): usize {
|
function align_up(x: usize, align: usize): usize {
|
||||||
assert(!(align & (align - 1)), "must align to a power of two");
|
assert(!(align & (align - 1)), "must align to a power of two");
|
||||||
@ -185,8 +441,8 @@ function align_ptr(ptr: usize, align: usize): usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjust an allocation size to be aligned to word size, and no smaller
|
* Adjusts an allocation size to be aligned to word size, and no smaller than
|
||||||
* than internal minimum.
|
* the internal minimum.
|
||||||
*/
|
*/
|
||||||
function adjust_request_size(size: usize, align: usize): usize {
|
function adjust_request_size(size: usize, align: usize): usize {
|
||||||
var adjust: usize = 0;
|
var adjust: usize = 0;
|
||||||
@ -197,15 +453,14 @@ function adjust_request_size(size: usize, align: usize): usize {
|
|||||||
return adjust;
|
return adjust;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSF utility functions. In most cases, these are direct translations of
|
// TLSF utility functions. In most cases, these are direct translations of the
|
||||||
// the documentation found in the white paper.
|
// documentation found in the white paper.
|
||||||
|
|
||||||
var fl_out: i32, sl_out: i32;
|
var fl_out: i32, sl_out: i32;
|
||||||
|
|
||||||
function mapping_insert(size: usize): void {
|
function mapping_insert(size: usize): void {
|
||||||
var fl: i32, sl: i32;
|
var fl: i32, sl: i32;
|
||||||
if (size < SMALL_BLOCK_SIZE) {
|
if (size < SMALL_BLOCK_SIZE) { // Store small blocks in first list.
|
||||||
// Store small blocks in first list.
|
|
||||||
fl = 0;
|
fl = 0;
|
||||||
sl = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
|
sl = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
|
||||||
} else {
|
} else {
|
||||||
@ -225,17 +480,14 @@ function mapping_search(size: usize): void {
|
|||||||
mapping_insert(size);
|
mapping_insert(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
function search_suitable_block(control: Control, fl: i32, sl: i32): BlockHeader {
|
function find_suitable_block(control: Control, fl: i32, sl: i32): BlockHeader {
|
||||||
// First, search for a block in the list associated with the given
|
// Search for a block in the list associated with the given fl/sl index
|
||||||
// fl/sl index.
|
|
||||||
var sl_map = control.sl_bitmap(fl) & (<u32>~0 << sl);
|
var sl_map = control.sl_bitmap(fl) & (<u32>~0 << sl);
|
||||||
if (!sl_map) {
|
if (!sl_map) {
|
||||||
// No block exists. Search in the next largest first-level list.
|
// If no block exists, search in the next largest first-level list
|
||||||
var fl_map = control.fl_bitmap & (<u32>~0 << (fl + 1));
|
var fl_map = control.fl_bitmap & (<u32>~0 << (fl + 1));
|
||||||
if (!fl_map) {
|
if (!fl_map)
|
||||||
// No free blocks available, memory has been exhausted.
|
return changetype<BlockHeader>(0); // Memory pool has been exhausted
|
||||||
return changetype<BlockHeader>(0);
|
|
||||||
}
|
|
||||||
fl = ffs<u32>(fl_map);
|
fl = ffs<u32>(fl_map);
|
||||||
fl_out = fl;
|
fl_out = fl;
|
||||||
sl_map = control.sl_bitmap(fl);
|
sl_map = control.sl_bitmap(fl);
|
||||||
@ -243,240 +495,57 @@ function search_suitable_block(control: Control, fl: i32, sl: i32): BlockHeader
|
|||||||
assert(sl_map, "internal error - second level bitmap is null");
|
assert(sl_map, "internal error - second level bitmap is null");
|
||||||
sl = ffs<u32>(sl_map);
|
sl = ffs<u32>(sl_map);
|
||||||
sl_out = sl;
|
sl_out = sl;
|
||||||
// Return the first block in the free list.
|
return control.blocks(fl, sl); // First block in the free list
|
||||||
return control.blocks(fl, sl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove a free block from the free list.*/
|
// Exported interface
|
||||||
function remove_free_block(control: Control, block: BlockHeader, fl: i32, sl: i32): void {
|
|
||||||
var prev = block.prev_free;
|
|
||||||
var next = block.next_free;
|
|
||||||
assert(prev, "prev_free field can not be null");
|
|
||||||
assert(next, "next_free field can not be null");
|
|
||||||
next.prev_free = prev;
|
|
||||||
prev.next_free = next;
|
|
||||||
|
|
||||||
if (control.blocks(fl, sl) == block) {
|
var TLSF: Control;
|
||||||
control.blocks_set(fl, sl, next);
|
|
||||||
if (next == control) {
|
/** Requests more memory from the host environment. */
|
||||||
control.sl_bitmap_set(fl, control.sl_bitmap(fl) & ~(1 << sl));
|
function request_memory(size: usize): void {
|
||||||
if (!control.sl_bitmap(fl)) {
|
if (size & 0xffff) // Round size up to a full page
|
||||||
control.fl_bitmap &= ~(1 << fl);
|
size = (size | 0xffff) + 1;
|
||||||
|
// At least double the memory for efficiency
|
||||||
|
var prev_pages = grow_memory(max<u32>(current_memory(), <u32>size >> 16));
|
||||||
|
if (prev_pages < 0)
|
||||||
|
unreachable(); // Out of host memory. This is bad.
|
||||||
|
var next_pages = current_memory();
|
||||||
|
TLSF.addPool(<usize>prev_pages << 16, <usize>(next_pages - prev_pages) << 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Allocates a chunk of memory of the specified size and returns a pointer to it. */
|
||||||
|
export function allocate_memory(size: usize): usize {
|
||||||
|
if (!TLSF) // Initialize TLSF when actually used so it DCEs just fine otherwise
|
||||||
|
TLSF = Control.create(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
|
||||||
|
var control = changetype<Control>(TLSF);
|
||||||
|
var adjust = adjust_request_size(size, ALIGN_SIZE);
|
||||||
|
var block = control.locateFreeBlock(adjust);
|
||||||
|
if (!block && size > 0) {
|
||||||
|
request_memory(adjust);
|
||||||
|
block = control.locateFreeBlock(adjust);
|
||||||
}
|
}
|
||||||
|
return control.prepareUsedBlock(block, adjust);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disposes a chunk of memory by its pointer. */
|
||||||
|
export function free_memory(ptr: usize): void {
|
||||||
|
if (TLSF && ptr) {
|
||||||
|
var control = changetype<Control>(TLSF);
|
||||||
|
var block = BlockHeader.fromPayloadPtr(ptr);
|
||||||
|
assert(!block.isFree, "block already marked as free");
|
||||||
|
block.markAsFree();
|
||||||
|
block = control.mergePrevBlock(block);
|
||||||
|
block = control.mergeNextBlock(block);
|
||||||
|
control.insertBlock(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert a free block into the free block list. */
|
// Extra debugging
|
||||||
function insert_free_block(control: Control, block: BlockHeader, fl: i32, sl: i32): void {
|
|
||||||
var current = control.blocks(fl, sl);
|
|
||||||
assert(current, "free list cannot have a null entry");
|
|
||||||
assert(block, "cannot insert a null entry into the free list");
|
|
||||||
block.next_free = current;
|
|
||||||
block.prev_free = control;
|
|
||||||
current.prev_free = block;
|
|
||||||
|
|
||||||
assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not aligned properly");
|
assert(sizeof<u32>() * 8 >= SL_INDEX_COUNT, "SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type");
|
||||||
|
assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT, "invalid alignment");
|
||||||
// Insert the new block at the head of the list, and mark the first-
|
assert(test_ffs_fls() == 0, "ffs/fls are not working properly");
|
||||||
// and second-level bitmaps appropriately.
|
|
||||||
control.blocks_set(fl, sl, block);
|
|
||||||
control.fl_bitmap |= (1 << fl);
|
|
||||||
control.sl_bitmap_set(fl, control.sl_bitmap(fl) | (1 << sl))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove a given block from the free list. */
|
|
||||||
function block_remove(control: Control, block: BlockHeader): void {
|
|
||||||
mapping_insert(block_size(block));
|
|
||||||
remove_free_block(control, block, fl_out, sl_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Insert a given block into the free list. */
|
|
||||||
function block_insert(control: Control, block: BlockHeader): void {
|
|
||||||
mapping_insert(block_size(block));
|
|
||||||
insert_free_block(control, block, fl_out, sl_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_can_split(block: BlockHeader, size: usize): bool {
|
|
||||||
return block_size(block) >= sizeof_block_header_t + size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Split a block into two, the second of which is free. */
|
|
||||||
function block_split(block: BlockHeader, size: usize): BlockHeader {
|
|
||||||
// Calculate the amount of space left in the remaining block.
|
|
||||||
var remaining = offset_to_block(block_to_ptr(block), size - block_header_overhead);
|
|
||||||
var remain_size = block_size(block) - (size + block_header_overhead);
|
|
||||||
|
|
||||||
assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE), "remaining block not aligned properly");
|
|
||||||
assert(block_size(block) == remain_size + size + block_header_overhead);
|
|
||||||
block_set_size(remaining, remain_size);
|
|
||||||
assert(block_size(remaining) >= block_size_min, "block split with invalid size");
|
|
||||||
|
|
||||||
block_set_size(block, size);
|
|
||||||
block_mark_as_free(remaining);
|
|
||||||
return remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Absorb a free block's storage into an adjacent previous free block. */
|
|
||||||
function block_absorb(prev: BlockHeader, block: BlockHeader): BlockHeader {
|
|
||||||
assert(!block_is_last(prev), "previous block can't be last");
|
|
||||||
// Note: Leaves flags untouched.
|
|
||||||
prev.tagged_size += block_size(block) + block_header_overhead;
|
|
||||||
block_link_next(prev);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Merge a just-freed block with an adjacent previous free block. */
|
|
||||||
function block_merge_prev(control: Control, block: BlockHeader): BlockHeader {
|
|
||||||
if (block_is_prev_free(block)) {
|
|
||||||
var prev = block_prev(block);
|
|
||||||
assert(prev, "prev physical block can't be null");
|
|
||||||
assert(block_is_free(prev), "prev block is not free though marked as such");
|
|
||||||
block_remove(control, prev);
|
|
||||||
block = block_absorb(prev, block);
|
|
||||||
}
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Merge a just-freed block with an adjacent free block. */
|
|
||||||
function block_merge_next(control: Control, block: BlockHeader): BlockHeader {
|
|
||||||
var next = block_next(block);
|
|
||||||
assert(next, "next physical block can't be null");
|
|
||||||
if (block_is_free(next)) {
|
|
||||||
assert(!block_is_last(block), "previous block can't be last");
|
|
||||||
block_remove(control, next);
|
|
||||||
block = block_absorb(block, next);
|
|
||||||
}
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trim any trailing block space off the end of a block, return to pool. */
|
|
||||||
function block_trim_free(control: Control, block: BlockHeader, size: usize): void {
|
|
||||||
assert(block_is_free(block), "block must be free");
|
|
||||||
if (block_can_split(block, size)) {
|
|
||||||
var remaining_block = block_split(block, size);
|
|
||||||
block_link_next(block);
|
|
||||||
block_set_prev_free(remaining_block);
|
|
||||||
block_insert(control, remaining_block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trim any trailing block space off the end of a used block, return to pool. */
|
|
||||||
function block_trim_used(control: Control, block: BlockHeader, size: usize): void {
|
|
||||||
assert(!block_is_free(block), "block must be used");
|
|
||||||
if (block_can_split(block, size)) {
|
|
||||||
// If the next block is free, we must coalesce.
|
|
||||||
var remaining_block = block_split(block, size);
|
|
||||||
block_set_prev_used(remaining_block);
|
|
||||||
remaining_block = block_merge_next(control, remaining_block);
|
|
||||||
block_insert(control, remaining_block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_trim_free_leading(control: Control, block: BlockHeader, size: usize): BlockHeader {
|
|
||||||
var remaining_block = block;
|
|
||||||
if (block_can_split(block, size)) {
|
|
||||||
remaining_block = block_split(block, size - block_header_overhead);
|
|
||||||
block_set_prev_free(remaining_block);
|
|
||||||
block_link_next(block);
|
|
||||||
block_insert(control, block);
|
|
||||||
}
|
|
||||||
return remaining_block;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_locate_free(control: Control, size: usize): BlockHeader {
|
|
||||||
var index: u64 = 0;
|
|
||||||
var block: BlockHeader = changetype<BlockHeader>(0);
|
|
||||||
if (size) {
|
|
||||||
mapping_search(size);
|
|
||||||
if (fl_out < FL_INDEX_MAX) {
|
|
||||||
block = search_suitable_block(control, fl_out, sl_out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (block) {
|
|
||||||
assert(block_size(block) >= size);
|
|
||||||
remove_free_block(control, block, fl_out, sl_out);
|
|
||||||
}
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
function block_prepare_used(control: Control, block: BlockHeader, size: usize): usize {
|
|
||||||
var p: usize = 0;
|
|
||||||
if (block) {
|
|
||||||
assert(size, "size must be non-zero");
|
|
||||||
block_trim_free(control, block, size);
|
|
||||||
block_mark_as_used(block);
|
|
||||||
p = block_to_ptr(block);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clear structure and point all empty lists at the null block. */
|
|
||||||
function control_construct(control: Control): void {
|
|
||||||
control.next_free = control;
|
|
||||||
control.prev_free = control;
|
|
||||||
control.fl_bitmap = 0;
|
|
||||||
for (var i = 0; i < FL_INDEX_COUNT; ++i) {
|
|
||||||
control.sl_bitmap_set(i, 0);
|
|
||||||
for (var j = 0; j < SL_INDEX_COUNT; ++j) {
|
|
||||||
control.blocks_set(i, j, control);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var TLSF: usize = 0;
|
|
||||||
|
|
||||||
function create(mem: usize): usize {
|
|
||||||
// Verify ffs/fls work properly
|
|
||||||
assert(!test_ffs_fls());
|
|
||||||
// SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type
|
|
||||||
assert(sizeof<u32>() * 8 >= SL_INDEX_COUNT);
|
|
||||||
// Ensure we've properly tuned our sizes.
|
|
||||||
assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
|
|
||||||
if ((mem % ALIGN_SIZE) != 0)
|
|
||||||
throw new RangeError("Memory must be aligned");
|
|
||||||
control_construct(changetype<Control>(mem));
|
|
||||||
return mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_with_pool(mem: usize, bytes: usize): usize {
|
|
||||||
var tlsf = create(mem);
|
|
||||||
add_pool(tlsf, mem + sizeof_control_t, bytes - sizeof_control_t);
|
|
||||||
return tlsf;
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_pool(tlsf: usize, mem: usize, bytes: usize): usize {
|
|
||||||
var block: BlockHeader;
|
|
||||||
var next: BlockHeader;
|
|
||||||
|
|
||||||
// Overhead of the TLSF structures in a given memory block, equal
|
|
||||||
// to the overhead of the free block and the sentinel block.
|
|
||||||
const pool_overhead: usize = 2 * block_header_overhead;
|
|
||||||
var pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
|
|
||||||
if ((mem % ALIGN_SIZE) != 0)
|
|
||||||
throw new RangeError("Memory must be aligned");
|
|
||||||
if (pool_bytes < block_size_min || pool_bytes > block_size_max)
|
|
||||||
throw new RangeError("Memory size must be between min and max");
|
|
||||||
|
|
||||||
// Create the main free block. Offset the start of the block slightly
|
|
||||||
// so that the prev_phys_block field falls outside of the pool -
|
|
||||||
// it will never be used.
|
|
||||||
block = offset_to_block(mem, -block_header_overhead);
|
|
||||||
block_set_size(block, pool_bytes);
|
|
||||||
block_set_free(block);
|
|
||||||
block_set_prev_used(block);
|
|
||||||
block_insert(changetype<Control>(tlsf), block);
|
|
||||||
|
|
||||||
// Split the block to create a zero-size sentinel block.
|
|
||||||
next = block_link_next(block);
|
|
||||||
block_set_size(next, 0);
|
|
||||||
block_set_used(next);
|
|
||||||
block_set_prev_free(next);
|
|
||||||
|
|
||||||
return mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
|
|
||||||
function test_ffs_fls(): i32 {
|
function test_ffs_fls(): i32 {
|
||||||
var rv = 0;
|
var rv = 0;
|
||||||
@ -494,9 +563,9 @@ function test_ffs_fls(): i32 {
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function check(): i32 {
|
function check(): i32 {
|
||||||
if (!TLSF)
|
if (!TLSF)
|
||||||
TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
|
TLSF = Control.create(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
|
||||||
var control = changetype<Control>(TLSF);
|
var control = changetype<Control>(TLSF);
|
||||||
var status = 0;
|
var status = 0;
|
||||||
for (var i = 0; i < FL_INDEX_COUNT; ++i) {
|
for (var i = 0; i < FL_INDEX_COUNT; ++i) {
|
||||||
@ -518,17 +587,17 @@ export function check(): i32 {
|
|||||||
if (!assert(block != control, "block should not be null"))
|
if (!assert(block != control, "block should not be null"))
|
||||||
--status;
|
--status;
|
||||||
while (block != control) {
|
while (block != control) {
|
||||||
if (!assert(block_is_free(block), "block should be free"))
|
if (!assert(block.isFree, "block should be free"))
|
||||||
--status;
|
--status;
|
||||||
if (!assert(!block_is_prev_free(block), "blocks should have coalesced"))
|
if (!assert(!block.isPrevFree, "blocks should have coalesced"))
|
||||||
--status;
|
--status;
|
||||||
if (!assert(!block_is_free(block_next(block)), "blocks should have coalesced"))
|
if (!assert(!block.next.isFree, "blocks should have coalesced"))
|
||||||
--status;
|
--status;
|
||||||
if (!assert(block_is_prev_free(block_next(block)), "block should be free"))
|
if (!assert(block.next.isPrevFree, "block should be free"))
|
||||||
--status;
|
--status;
|
||||||
if (!assert(block_size(block) >= block_size_min, "block not minimum size"))
|
if (!assert(block.size >= block_size_min, "block not minimum size"))
|
||||||
--status;
|
--status;
|
||||||
mapping_insert(block_size(block));
|
mapping_insert(block.size);
|
||||||
if (!assert(fl_out == i && sl_out == j, "block size indexed in wrong list"))
|
if (!assert(fl_out == i && sl_out == j, "block size indexed in wrong list"))
|
||||||
--status;
|
--status;
|
||||||
block = block.next_free;
|
block = block.next_free;
|
||||||
@ -539,42 +608,40 @@ export function check(): i32 {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exported interface
|
var integrity_prev_status: i32;
|
||||||
|
var integrity_status: i32;
|
||||||
|
|
||||||
function request_memory(size: usize): void {
|
function integrity_walker(ptr: usize, size: usize, used: bool): void {
|
||||||
// round size up to a full page
|
var block = BlockHeader.fromPayloadPtr(ptr);
|
||||||
if (size & 0xffff)
|
var this_prev_status = block.isPrevFree;
|
||||||
size = (size | 0xffff) + 1;
|
var this_status = block.isFree;
|
||||||
// at least double memory for efficiency
|
var this_block_size = block.size;
|
||||||
var prev_pages = grow_memory(max<u32>(current_memory(), <u32>size >> 16));
|
|
||||||
if (prev_pages < 0) // out of host memory
|
var status = 0;
|
||||||
unreachable();
|
if (!assert(integrity_prev_status == this_prev_status, "prev status incorrect"))
|
||||||
var next_pages = current_memory();
|
--status;
|
||||||
add_pool(TLSF, <usize>prev_pages << 16, <usize>(next_pages - prev_pages) << 16);
|
if (!assert(size == this_block_size, "block size incorrect"))
|
||||||
|
--status;
|
||||||
|
integrity_prev_status = this_status;
|
||||||
|
integrity_status += status;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function allocate_memory(size: usize): usize {
|
function check_pool(pool: usize): i32 {
|
||||||
if (!TLSF) {
|
if (pool < 0x10000) { // first pool
|
||||||
TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
|
pool = changetype<usize>(TLSF) + sizeof_control_t;
|
||||||
}
|
}
|
||||||
var control = changetype<Control>(TLSF);
|
// inlined walk_bool with static integrity_walker
|
||||||
var adjust = adjust_request_size(size, ALIGN_SIZE);
|
integrity_prev_status = integrity_status = 0;
|
||||||
var block = block_locate_free(control, adjust);
|
var block = BlockHeader.fromOffset(pool, -block_header_overhead);
|
||||||
if (!block && adjust > 0) {
|
while (block && !block.isLast) {
|
||||||
request_memory(adjust);
|
integrity_walker(
|
||||||
block = block_locate_free(control, adjust);
|
block.toPayloadPtr(),
|
||||||
|
block.size,
|
||||||
|
!block.isFree
|
||||||
|
);
|
||||||
|
block = block.next;
|
||||||
}
|
}
|
||||||
return block_prepare_used(control, block, adjust);
|
return integrity_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function free_memory(ptr: usize): void {
|
// export { check, check_pool, set_memory };
|
||||||
if (TLSF && ptr) {
|
|
||||||
var control = changetype<Control>(TLSF);
|
|
||||||
var block = block_from_ptr(ptr);
|
|
||||||
assert(!block_is_free(block), "block already marked as free");
|
|
||||||
block_mark_as_free(block);
|
|
||||||
block = block_merge_prev(control, block);
|
|
||||||
block = block_merge_next(control, block);
|
|
||||||
block_insert(control, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -21,6 +21,8 @@ function test(file) {
|
|||||||
for (var i = 0; i < 2048; ++i) {
|
for (var i = 0; i < 2048; ++i) {
|
||||||
var size = i * 61;
|
var size = i * 61;
|
||||||
ptr = tlsf.allocate_memory(size);
|
ptr = tlsf.allocate_memory(size);
|
||||||
|
if (tlsf.set_memory)
|
||||||
|
tlsf.set_memory(ptr, ptr % 256, size); // slow
|
||||||
// immediately free every 4th
|
// immediately free every 4th
|
||||||
if (!(i % 4)) {
|
if (!(i % 4)) {
|
||||||
tlsf.free_memory(ptr);
|
tlsf.free_memory(ptr);
|
||||||
@ -33,7 +35,10 @@ function test(file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (tlsf.check)
|
||||||
tlsf.check();
|
tlsf.check();
|
||||||
|
if (tlsf.check_pool)
|
||||||
|
tlsf.check_pool(0);
|
||||||
// clean up by randomly freeing all blocks
|
// clean up by randomly freeing all blocks
|
||||||
while (ptrs.length) {
|
while (ptrs.length) {
|
||||||
ptr = ptrs.splice((Math.random() * ptrs.length)|0, 1)[0];
|
ptr = ptrs.splice((Math.random() * ptrs.length)|0, 1)[0];
|
||||||
@ -42,7 +47,10 @@ function test(file) {
|
|||||||
if (memSize && memSize != tlsf.memory.buffer.byteLength)
|
if (memSize && memSize != tlsf.memory.buffer.byteLength)
|
||||||
throw new Error("memory should not grow multiple times: " + memSize + " != " + tlsf.memory.buffer.byteLength);
|
throw new Error("memory should not grow multiple times: " + memSize + " != " + tlsf.memory.buffer.byteLength);
|
||||||
memSize = tlsf.memory.buffer.byteLength;
|
memSize = tlsf.memory.buffer.byteLength;
|
||||||
|
if (tlsf.check)
|
||||||
tlsf.check();
|
tlsf.check();
|
||||||
|
if (tlsf.check_pool)
|
||||||
|
tlsf.check_pool(0);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
mem(tlsf.memory, 0, 4096);
|
mem(tlsf.memory, 0, 4096);
|
||||||
|
@ -2693,6 +2693,7 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
this.currentType = getterInstance.returnType;
|
this.currentType = getterInstance.returnType;
|
||||||
if (getterInstance.isInstance) {
|
if (getterInstance.isInstance) {
|
||||||
var targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32)
|
var targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32)
|
||||||
|
this.currentType = getterInstance.returnType;
|
||||||
return this.makeCall(getterInstance, [ targetExpr ]);
|
return this.makeCall(getterInstance, [ targetExpr ]);
|
||||||
} else
|
} else
|
||||||
return this.makeCall(getterInstance);
|
return this.makeCall(getterInstance);
|
||||||
|
@ -224,8 +224,10 @@ export class Program extends DiagnosticEmitter {
|
|||||||
if (!currentExport)
|
if (!currentExport)
|
||||||
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.member.externalIdentifier.range, (<StringLiteralExpression>(<ExportStatement>queuedExport.member.parent).path).value, queuedExport.member.externalIdentifier.name);
|
this.error(DiagnosticCode.Module_0_has_no_exported_member_1, queuedExport.member.externalIdentifier.range, (<StringLiteralExpression>(<ExportStatement>queuedExport.member.parent).path).value, queuedExport.member.externalIdentifier.name);
|
||||||
} else {
|
} else {
|
||||||
element = this.elements.get(currentExport.referencedName);
|
if (
|
||||||
if (element)
|
(element = this.elements.get(currentExport.referencedName)) || // normal export
|
||||||
|
(element = this.elements.get(currentExport.member.identifier.name)) // stdlib re-export
|
||||||
|
)
|
||||||
this.exports.set(exportName, element);
|
this.exports.set(exportName, element);
|
||||||
else
|
else
|
||||||
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.range, queuedExport.member.identifier.name);
|
this.error(DiagnosticCode.Cannot_find_name_0, queuedExport.member.range, queuedExport.member.identifier.name);
|
||||||
@ -273,8 +275,10 @@ export class Program extends DiagnosticEmitter {
|
|||||||
if (hasDecorator("global", declaration.decorators) || (declaration.range.source.isStdlib && assert(declaration.parent).kind == NodeKind.SOURCE && element.isExported)) {
|
if (hasDecorator("global", declaration.decorators) || (declaration.range.source.isStdlib && assert(declaration.parent).kind == NodeKind.SOURCE && element.isExported)) {
|
||||||
if (this.elements.has(declaration.name.name))
|
if (this.elements.has(declaration.name.name))
|
||||||
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, element.internalName);
|
this.error(DiagnosticCode.Duplicate_identifier_0, declaration.name.range, element.internalName);
|
||||||
else
|
else {
|
||||||
this.elements.set(declaration.name.name, element);
|
this.elements.set(declaration.name.name, element);
|
||||||
|
this.exports.set(declaration.name.name, element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1029,15 +1033,35 @@ export class Program extends DiagnosticEmitter {
|
|||||||
var propertyName = propertyAccess.property.name;
|
var propertyName = propertyAccess.property.name;
|
||||||
var targetType: Type;
|
var targetType: Type;
|
||||||
var member: Element | null;
|
var member: Element | null;
|
||||||
|
|
||||||
|
// Resolve variable-likes to their class type first
|
||||||
switch (target.kind) {
|
switch (target.kind) {
|
||||||
|
|
||||||
case ElementKind.GLOBAL:
|
case ElementKind.GLOBAL:
|
||||||
case ElementKind.LOCAL:
|
case ElementKind.LOCAL:
|
||||||
case ElementKind.FIELD:
|
case ElementKind.FIELD:
|
||||||
if (!(targetType = (<VariableLikeElement>target).type).classType)
|
if (!(targetType = (<VariableLikeElement>target).type).classType) {
|
||||||
break;
|
console.log(propertyAccess.property.name + " on " + targetType);
|
||||||
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, targetType.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
target = <Class>targetType.classType;
|
target = <Class>targetType.classType;
|
||||||
// fall-through
|
break;
|
||||||
|
|
||||||
|
case ElementKind.PROPERTY:
|
||||||
|
var getter = assert((<Property>target).getterPrototype).resolve(); // reports
|
||||||
|
if (!getter)
|
||||||
|
return null;
|
||||||
|
if (!(targetType = getter.returnType).classType) {
|
||||||
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, targetType.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
target = <Class>targetType.classType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the member within
|
||||||
|
switch (target.kind) {
|
||||||
|
|
||||||
case ElementKind.CLASS_PROTOTYPE:
|
case ElementKind.CLASS_PROTOTYPE:
|
||||||
case ElementKind.CLASS:
|
case ElementKind.CLASS:
|
||||||
@ -1066,6 +1090,7 @@ export class Program extends DiagnosticEmitter {
|
|||||||
return resolvedElement.set(member).withTarget(target, targetExpression);
|
return resolvedElement.set(member).withTarget(target, targetExpression);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, target.internalName);
|
this.error(DiagnosticCode.Property_0_does_not_exist_on_type_1, propertyAccess.property.range, propertyName, target.internalName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1179,7 +1204,7 @@ export enum ElementKind {
|
|||||||
FIELD_PROTOTYPE,
|
FIELD_PROTOTYPE,
|
||||||
/** A {@link Field}. */
|
/** A {@link Field}. */
|
||||||
FIELD,
|
FIELD,
|
||||||
/** A {@link PropertyContainer}. */
|
/** A {@link Property}. */
|
||||||
PROPERTY,
|
PROPERTY,
|
||||||
/** A {@link Namespace}. */
|
/** A {@link Namespace}. */
|
||||||
NAMESPACE
|
NAMESPACE
|
||||||
@ -1789,10 +1814,12 @@ export class Function extends Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A yet unresolved instance field prototype. */
|
/** A yet unresolved instance field prototype. */
|
||||||
export class FieldPrototype extends VariableLikeElement {
|
export class FieldPrototype extends Element {
|
||||||
|
|
||||||
kind = ElementKind.FIELD_PROTOTYPE;
|
kind = ElementKind.FIELD_PROTOTYPE;
|
||||||
|
|
||||||
|
/** Declaration reference. */
|
||||||
|
declaration: FieldDeclaration | null;
|
||||||
/** Parent class prototype. */
|
/** Parent class prototype. */
|
||||||
classPrototype: ClassPrototype;
|
classPrototype: ClassPrototype;
|
||||||
|
|
||||||
@ -1821,18 +1848,12 @@ export class FieldPrototype extends VariableLikeElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A resolved instance field. */
|
/** A resolved instance field. */
|
||||||
export class Field extends Element {
|
export class Field extends VariableLikeElement {
|
||||||
|
|
||||||
kind = ElementKind.FIELD;
|
kind = ElementKind.FIELD;
|
||||||
|
|
||||||
/** Field prototype reference. */
|
/** Field prototype reference. */
|
||||||
prototype: FieldPrototype;
|
prototype: FieldPrototype;
|
||||||
/** Resolved type. */
|
|
||||||
type: Type;
|
|
||||||
/** Constant integer value, if a constant static integer. */
|
|
||||||
constantIntegerValue: I64 | null = null;
|
|
||||||
/** Constant float value, if a constant static float. */
|
|
||||||
constantFloatValue: f64 = 0;
|
|
||||||
/** Field memory offset, if an instance field. */
|
/** Field memory offset, if an instance field. */
|
||||||
memoryOffset: i32 = -1;
|
memoryOffset: i32 = -1;
|
||||||
|
|
||||||
|
@ -4098,19 +4098,34 @@
|
|||||||
GLOBAL: std/array/arr
|
GLOBAL: std/array/arr
|
||||||
GLOBAL: std/array/i
|
GLOBAL: std/array/i
|
||||||
[program.exports]
|
[program.exports]
|
||||||
|
CLASS_PROTOTYPE: Array
|
||||||
CLASS_PROTOTYPE: std:array/Array
|
CLASS_PROTOTYPE: std:array/Array
|
||||||
|
CLASS_PROTOTYPE: CArray
|
||||||
CLASS_PROTOTYPE: std:array/CArray
|
CLASS_PROTOTYPE: std:array/CArray
|
||||||
|
CLASS_PROTOTYPE: Error
|
||||||
CLASS_PROTOTYPE: std:error/Error
|
CLASS_PROTOTYPE: std:error/Error
|
||||||
|
CLASS_PROTOTYPE: RangeError
|
||||||
CLASS_PROTOTYPE: std:error/RangeError
|
CLASS_PROTOTYPE: std:error/RangeError
|
||||||
|
FUNCTION_PROTOTYPE: allocate_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
||||||
|
FUNCTION_PROTOTYPE: free_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/free_memory
|
FUNCTION_PROTOTYPE: std:heap/free_memory
|
||||||
|
FUNCTION_PROTOTYPE: move_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/move_memory
|
FUNCTION_PROTOTYPE: std:heap/move_memory
|
||||||
|
FUNCTION_PROTOTYPE: set_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/set_memory
|
FUNCTION_PROTOTYPE: std:heap/set_memory
|
||||||
|
FUNCTION_PROTOTYPE: compare_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
||||||
|
CLASS_PROTOTYPE: Map
|
||||||
CLASS_PROTOTYPE: std:map/Map
|
CLASS_PROTOTYPE: std:map/Map
|
||||||
|
CLASS_PROTOTYPE: RegExp
|
||||||
CLASS_PROTOTYPE: std:regexp/RegExp
|
CLASS_PROTOTYPE: std:regexp/RegExp
|
||||||
|
CLASS_PROTOTYPE: Set
|
||||||
CLASS_PROTOTYPE: std:set/Set
|
CLASS_PROTOTYPE: std:set/Set
|
||||||
|
CLASS_PROTOTYPE: String
|
||||||
CLASS_PROTOTYPE: std:string/String
|
CLASS_PROTOTYPE: std:string/String
|
||||||
|
FUNCTION_PROTOTYPE: parseInt
|
||||||
FUNCTION_PROTOTYPE: std:string/parseInt
|
FUNCTION_PROTOTYPE: std:string/parseInt
|
||||||
|
FUNCTION_PROTOTYPE: parseFloat
|
||||||
FUNCTION_PROTOTYPE: std:string/parseFloat
|
FUNCTION_PROTOTYPE: std:string/parseFloat
|
||||||
;)
|
;)
|
||||||
|
@ -290,19 +290,34 @@
|
|||||||
FUNCTION_PROTOTYPE: parseFloat
|
FUNCTION_PROTOTYPE: parseFloat
|
||||||
GLOBAL: std/carray/arr
|
GLOBAL: std/carray/arr
|
||||||
[program.exports]
|
[program.exports]
|
||||||
|
CLASS_PROTOTYPE: Array
|
||||||
CLASS_PROTOTYPE: std:array/Array
|
CLASS_PROTOTYPE: std:array/Array
|
||||||
|
CLASS_PROTOTYPE: CArray
|
||||||
CLASS_PROTOTYPE: std:array/CArray
|
CLASS_PROTOTYPE: std:array/CArray
|
||||||
|
CLASS_PROTOTYPE: Error
|
||||||
CLASS_PROTOTYPE: std:error/Error
|
CLASS_PROTOTYPE: std:error/Error
|
||||||
|
CLASS_PROTOTYPE: RangeError
|
||||||
CLASS_PROTOTYPE: std:error/RangeError
|
CLASS_PROTOTYPE: std:error/RangeError
|
||||||
|
FUNCTION_PROTOTYPE: allocate_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
||||||
|
FUNCTION_PROTOTYPE: free_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/free_memory
|
FUNCTION_PROTOTYPE: std:heap/free_memory
|
||||||
|
FUNCTION_PROTOTYPE: move_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/move_memory
|
FUNCTION_PROTOTYPE: std:heap/move_memory
|
||||||
|
FUNCTION_PROTOTYPE: set_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/set_memory
|
FUNCTION_PROTOTYPE: std:heap/set_memory
|
||||||
|
FUNCTION_PROTOTYPE: compare_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
||||||
|
CLASS_PROTOTYPE: Map
|
||||||
CLASS_PROTOTYPE: std:map/Map
|
CLASS_PROTOTYPE: std:map/Map
|
||||||
|
CLASS_PROTOTYPE: RegExp
|
||||||
CLASS_PROTOTYPE: std:regexp/RegExp
|
CLASS_PROTOTYPE: std:regexp/RegExp
|
||||||
|
CLASS_PROTOTYPE: Set
|
||||||
CLASS_PROTOTYPE: std:set/Set
|
CLASS_PROTOTYPE: std:set/Set
|
||||||
|
CLASS_PROTOTYPE: String
|
||||||
CLASS_PROTOTYPE: std:string/String
|
CLASS_PROTOTYPE: std:string/String
|
||||||
|
FUNCTION_PROTOTYPE: parseInt
|
||||||
FUNCTION_PROTOTYPE: std:string/parseInt
|
FUNCTION_PROTOTYPE: std:string/parseInt
|
||||||
|
FUNCTION_PROTOTYPE: parseFloat
|
||||||
FUNCTION_PROTOTYPE: std:string/parseFloat
|
FUNCTION_PROTOTYPE: std:string/parseFloat
|
||||||
;)
|
;)
|
||||||
|
@ -2884,19 +2884,34 @@
|
|||||||
GLOBAL: std/heap/ptr2
|
GLOBAL: std/heap/ptr2
|
||||||
GLOBAL: std/heap/i
|
GLOBAL: std/heap/i
|
||||||
[program.exports]
|
[program.exports]
|
||||||
|
CLASS_PROTOTYPE: Array
|
||||||
CLASS_PROTOTYPE: std:array/Array
|
CLASS_PROTOTYPE: std:array/Array
|
||||||
|
CLASS_PROTOTYPE: CArray
|
||||||
CLASS_PROTOTYPE: std:array/CArray
|
CLASS_PROTOTYPE: std:array/CArray
|
||||||
|
CLASS_PROTOTYPE: Error
|
||||||
CLASS_PROTOTYPE: std:error/Error
|
CLASS_PROTOTYPE: std:error/Error
|
||||||
|
CLASS_PROTOTYPE: RangeError
|
||||||
CLASS_PROTOTYPE: std:error/RangeError
|
CLASS_PROTOTYPE: std:error/RangeError
|
||||||
|
FUNCTION_PROTOTYPE: allocate_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
||||||
|
FUNCTION_PROTOTYPE: free_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/free_memory
|
FUNCTION_PROTOTYPE: std:heap/free_memory
|
||||||
|
FUNCTION_PROTOTYPE: move_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/move_memory
|
FUNCTION_PROTOTYPE: std:heap/move_memory
|
||||||
|
FUNCTION_PROTOTYPE: set_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/set_memory
|
FUNCTION_PROTOTYPE: std:heap/set_memory
|
||||||
|
FUNCTION_PROTOTYPE: compare_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
||||||
|
CLASS_PROTOTYPE: Map
|
||||||
CLASS_PROTOTYPE: std:map/Map
|
CLASS_PROTOTYPE: std:map/Map
|
||||||
|
CLASS_PROTOTYPE: RegExp
|
||||||
CLASS_PROTOTYPE: std:regexp/RegExp
|
CLASS_PROTOTYPE: std:regexp/RegExp
|
||||||
|
CLASS_PROTOTYPE: Set
|
||||||
CLASS_PROTOTYPE: std:set/Set
|
CLASS_PROTOTYPE: std:set/Set
|
||||||
|
CLASS_PROTOTYPE: String
|
||||||
CLASS_PROTOTYPE: std:string/String
|
CLASS_PROTOTYPE: std:string/String
|
||||||
|
FUNCTION_PROTOTYPE: parseInt
|
||||||
FUNCTION_PROTOTYPE: std:string/parseInt
|
FUNCTION_PROTOTYPE: std:string/parseInt
|
||||||
|
FUNCTION_PROTOTYPE: parseFloat
|
||||||
FUNCTION_PROTOTYPE: std:string/parseFloat
|
FUNCTION_PROTOTYPE: std:string/parseFloat
|
||||||
;)
|
;)
|
||||||
|
@ -2772,19 +2772,34 @@
|
|||||||
FUNCTION_PROTOTYPE: parseFloat
|
FUNCTION_PROTOTYPE: parseFloat
|
||||||
GLOBAL: std/set/set
|
GLOBAL: std/set/set
|
||||||
[program.exports]
|
[program.exports]
|
||||||
|
CLASS_PROTOTYPE: Array
|
||||||
CLASS_PROTOTYPE: std:array/Array
|
CLASS_PROTOTYPE: std:array/Array
|
||||||
|
CLASS_PROTOTYPE: CArray
|
||||||
CLASS_PROTOTYPE: std:array/CArray
|
CLASS_PROTOTYPE: std:array/CArray
|
||||||
|
CLASS_PROTOTYPE: Error
|
||||||
CLASS_PROTOTYPE: std:error/Error
|
CLASS_PROTOTYPE: std:error/Error
|
||||||
|
CLASS_PROTOTYPE: RangeError
|
||||||
CLASS_PROTOTYPE: std:error/RangeError
|
CLASS_PROTOTYPE: std:error/RangeError
|
||||||
|
FUNCTION_PROTOTYPE: allocate_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
FUNCTION_PROTOTYPE: std:heap/allocate_memory
|
||||||
|
FUNCTION_PROTOTYPE: free_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/free_memory
|
FUNCTION_PROTOTYPE: std:heap/free_memory
|
||||||
|
FUNCTION_PROTOTYPE: move_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/move_memory
|
FUNCTION_PROTOTYPE: std:heap/move_memory
|
||||||
|
FUNCTION_PROTOTYPE: set_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/set_memory
|
FUNCTION_PROTOTYPE: std:heap/set_memory
|
||||||
|
FUNCTION_PROTOTYPE: compare_memory
|
||||||
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
FUNCTION_PROTOTYPE: std:heap/compare_memory
|
||||||
|
CLASS_PROTOTYPE: Map
|
||||||
CLASS_PROTOTYPE: std:map/Map
|
CLASS_PROTOTYPE: std:map/Map
|
||||||
|
CLASS_PROTOTYPE: RegExp
|
||||||
CLASS_PROTOTYPE: std:regexp/RegExp
|
CLASS_PROTOTYPE: std:regexp/RegExp
|
||||||
|
CLASS_PROTOTYPE: Set
|
||||||
CLASS_PROTOTYPE: std:set/Set
|
CLASS_PROTOTYPE: std:set/Set
|
||||||
|
CLASS_PROTOTYPE: String
|
||||||
CLASS_PROTOTYPE: std:string/String
|
CLASS_PROTOTYPE: std:string/String
|
||||||
|
FUNCTION_PROTOTYPE: parseInt
|
||||||
FUNCTION_PROTOTYPE: std:string/parseInt
|
FUNCTION_PROTOTYPE: std:string/parseInt
|
||||||
|
FUNCTION_PROTOTYPE: parseFloat
|
||||||
FUNCTION_PROTOTYPE: std:string/parseFloat
|
FUNCTION_PROTOTYPE: std:string/parseFloat
|
||||||
;)
|
;)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user