mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-09 21:11:27 +00:00
Handle static readonly members like constants
This commit is contained in:
parent
1662950f3c
commit
461daab2a2
@ -1,5 +1,5 @@
|
|||||||
////////////// TLSF (Two-Level Segregate Fit) Memory Allocator ////////////////
|
////////////// TLSF (Two-Level Segregate Fit) Memory Allocator ////////////////
|
||||||
// based on https://github.com/mattconte/tlsf - BSD, see LICENSE file //
|
// based on https://github.com/mattconte/tlsf - BSD (see LICENSE file) //
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
@ -16,20 +16,33 @@ const FL_INDEX_SHIFT: u32 = SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2;
|
|||||||
const FL_INDEX_COUNT: u32 = FL_INDEX_MAX - FL_INDEX_SHIFT + 1;
|
const FL_INDEX_COUNT: u32 = FL_INDEX_MAX - FL_INDEX_SHIFT + 1;
|
||||||
const SMALL_BLOCK_SIZE: u32 = 1 << FL_INDEX_SHIFT;
|
const SMALL_BLOCK_SIZE: u32 = 1 << FL_INDEX_SHIFT;
|
||||||
|
|
||||||
// WebAssembly-specific ffs/fls
|
|
||||||
|
|
||||||
function ffs<T>(word: i32): i32 {
|
|
||||||
return word ? <i32>ctz(word) : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fls<T>(word: T): i32 {
|
|
||||||
return (<i32>sizeof<T>() << 3) - <i32>clz(word) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Block header structure. */
|
/** Block header structure. */
|
||||||
@explicit
|
@explicit
|
||||||
class BlockHeader {
|
class BlockHeader {
|
||||||
|
|
||||||
|
/////////////////////////////// Constants ///////////////////////////////////
|
||||||
|
|
||||||
|
// One block header is 16 bytes in WASM32 and 32 bytes in WASM64.
|
||||||
|
static readonly SIZE: usize = 4 * sizeof<usize>();
|
||||||
|
|
||||||
|
// Since block sizes are always at least a multiple of 4, the two least
|
||||||
|
// significant bits of the size field are used to store the block status.
|
||||||
|
static readonly FREE_BIT: usize = 1 << 0;
|
||||||
|
static readonly PREV_FREE_BIT: usize = 1 << 1;
|
||||||
|
|
||||||
|
// The size of the block header exposed to used blocks is the size field.
|
||||||
|
// The prev_phys_block field is stored *inside* the previous free block.
|
||||||
|
static readonly OVERHEAD: usize = sizeof<usize>();
|
||||||
|
|
||||||
|
// User data starts directly after the size field in a used block.
|
||||||
|
static readonly USERDATA_OFFSET: usize = sizeof<usize>() + sizeof<usize>();
|
||||||
|
|
||||||
|
// A free block must be large enough to store its header minus the size of
|
||||||
|
// the prev_phys_block field, and no larger than the number of addressable
|
||||||
|
// bits for FL_INDEX.
|
||||||
|
static readonly BLOCK_SIZE_MIN: usize = BlockHeader.SIZE - sizeof<usize>();
|
||||||
|
static readonly BLOCK_SIZE_MAX: usize = <usize>1 << FL_INDEX_MAX;
|
||||||
|
|
||||||
///////////////////////////////// Fields ////////////////////////////////////
|
///////////////////////////////// Fields ////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,12 +67,14 @@ class BlockHeader {
|
|||||||
|
|
||||||
/** Gets the size of this block, excluding the block header. */
|
/** Gets the size of this block, excluding the block header. */
|
||||||
get size(): usize {
|
get size(): usize {
|
||||||
return this.tagged_size & ~(block_header_free_bit | block_header_prev_free_bit);
|
const tags = BlockHeader.FREE_BIT | BlockHeader.PREV_FREE_BIT;
|
||||||
|
return this.tagged_size & ~tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the size of this block, retaining tagged bits. */
|
/** Sets the size of this block, retaining tagged bits. */
|
||||||
set size(size: usize) {
|
set size(size: usize) {
|
||||||
this.tagged_size = size | (this.tagged_size & (block_header_free_bit | block_header_prev_free_bit));
|
const tags = BlockHeader.FREE_BIT | BlockHeader.PREV_FREE_BIT;
|
||||||
|
this.tagged_size = size | (this.tagged_size & tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tests if this is the last block. */
|
/** Tests if this is the last block. */
|
||||||
@ -69,42 +84,42 @@ class BlockHeader {
|
|||||||
|
|
||||||
/** Tests if this block's status is 'free'. */
|
/** Tests if this block's status is 'free'. */
|
||||||
get isFree(): bool {
|
get isFree(): bool {
|
||||||
return (this.tagged_size & block_header_free_bit) == block_header_free_bit;
|
return (this.tagged_size & BlockHeader.FREE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets this block's status to 'free'. */
|
/** Tags this block as 'free'. Careful: Does not update adjacent blocks. */
|
||||||
setFree(): void {
|
tagFree(): void {
|
||||||
this.tagged_size |= block_header_free_bit;
|
this.tagged_size |= BlockHeader.FREE_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets this block's status to 'used'. */
|
/** Tags this block as 'used'. Careful: Does not update adjacent blocks. */
|
||||||
setUsed(): void {
|
tagUsed(): void {
|
||||||
this.tagged_size &= ~block_header_free_bit;
|
this.tagged_size &= ~BlockHeader.FREE_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tests if the previous block is free. */
|
/** Tests if the previous block is free. */
|
||||||
get isPrevFree(): bool {
|
get isPrevFree(): bool {
|
||||||
return (this.tagged_size & block_header_prev_free_bit) == block_header_prev_free_bit;
|
return (this.tagged_size & BlockHeader.PREV_FREE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the previous block's status to 'free'. */
|
/** Tags this block as 'prev is free'. Does not update adjacent blocks. */
|
||||||
setPrevFree(): void {
|
tagPrevFree(): void {
|
||||||
this.tagged_size |= block_header_prev_free_bit;
|
this.tagged_size |= BlockHeader.PREV_FREE_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the previous block's status to 'used'. */
|
/** Tags this block as 'prev is used'. Does not update adjacent blocks. */
|
||||||
setPrevUsed(): void {
|
tagPrevUsed(): void {
|
||||||
this.tagged_size &= ~block_header_prev_free_bit;
|
this.tagged_size &= ~BlockHeader.PREV_FREE_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the block header matching the specified payload pointer. */
|
/** Gets the block header matching the specified data pointer. */
|
||||||
static fromPayloadPtr(ptr: usize): BlockHeader {
|
static fromDataPtr(ptr: usize): BlockHeader {
|
||||||
return changetype<BlockHeader>(ptr - block_start_offset);
|
return changetype<BlockHeader>(ptr - BlockHeader.USERDATA_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the address of this block's payload. */
|
/** Returns the address of this block's data. */
|
||||||
toPayloadPtr(): usize {
|
toDataPtr(): usize {
|
||||||
return changetype<usize>(this) + block_start_offset;
|
return changetype<usize>(this) + BlockHeader.USERDATA_OFFSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the next block after this one using the specified size. */
|
/** Gets the next block after this one using the specified size. */
|
||||||
@ -114,14 +129,21 @@ class BlockHeader {
|
|||||||
|
|
||||||
/** Gets the previous block. */
|
/** Gets the previous block. */
|
||||||
get prev(): BlockHeader {
|
get prev(): BlockHeader {
|
||||||
assert(this.isPrevFree, "previous block must be free");
|
assert(this.isPrevFree,
|
||||||
|
"previous block must be free"
|
||||||
|
);
|
||||||
return this.prev_phys_block;
|
return this.prev_phys_block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the next block. */
|
/** Gets the next block. */
|
||||||
get next(): BlockHeader {
|
get next(): BlockHeader {
|
||||||
assert(!this.isLast, "last block has no next block");
|
assert(!this.isLast,
|
||||||
return BlockHeader.fromOffset(this.toPayloadPtr(), this.size - block_header_overhead);
|
"last block has no next block"
|
||||||
|
);
|
||||||
|
return BlockHeader.fromOffset(
|
||||||
|
this.toDataPtr(),
|
||||||
|
this.size - BlockHeader.OVERHEAD
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,97 +158,121 @@ class BlockHeader {
|
|||||||
/** Marks this block as being 'free'. */
|
/** Marks this block as being 'free'. */
|
||||||
markAsFree(): void {
|
markAsFree(): void {
|
||||||
var next = this.linkNext(); // Link the block to the next block, first.
|
var next = this.linkNext(); // Link the block to the next block, first.
|
||||||
next.setPrevFree();
|
next.tagPrevFree();
|
||||||
this.setFree();
|
this.tagFree();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Marks this block as being 'used'. */
|
/** Marks this block as being 'used'. */
|
||||||
markAsUsed(): void {
|
markAsUsed(): void {
|
||||||
var next = this.next;
|
var next = this.next;
|
||||||
next.setPrevUsed();
|
next.tagPrevUsed();
|
||||||
this.setUsed();
|
this.tagUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tests if this block can be splitted. */
|
/** Tests if this block can be splitted. */
|
||||||
canSplit(size: usize): bool {
|
canSplit(size: usize): bool {
|
||||||
return this.size >= sizeof_block_header_t + size;
|
return this.size >= BlockHeader.SIZE + size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Splits a block into two, the second of which is free. */
|
/* Splits a block into two, the second of which is free. */
|
||||||
split(size: usize): BlockHeader {
|
split(size: usize): BlockHeader {
|
||||||
// Calculate the amount of space left in the remaining block.
|
// Calculate the amount of space left in the remaining block.
|
||||||
var remaining = BlockHeader.fromOffset(this.toPayloadPtr(), size - block_header_overhead);
|
var remain = BlockHeader.fromOffset(
|
||||||
var remain_size = this.size - (size + block_header_overhead);
|
this.toDataPtr(),
|
||||||
assert(remaining.toPayloadPtr() == align_ptr(remaining.toPayloadPtr(), ALIGN_SIZE), "remaining block not aligned properly");
|
size - BlockHeader.OVERHEAD
|
||||||
assert(this.size == remain_size + size + block_header_overhead);
|
);
|
||||||
|
var remain_size = this.size - (size + BlockHeader.OVERHEAD);
|
||||||
remaining.size = remain_size;
|
assert(remain.toDataPtr() == align_ptr(remain.toDataPtr(), ALIGN_SIZE),
|
||||||
assert(remaining.size >= block_size_min, "block split with invalid size");
|
"remaining block not aligned properly"
|
||||||
|
);
|
||||||
|
remain.size = remain_size;
|
||||||
|
assert(remain.size >= BlockHeader.BLOCK_SIZE_MIN,
|
||||||
|
"block split with invalid size"
|
||||||
|
);
|
||||||
this.size = size;
|
this.size = size;
|
||||||
remaining.markAsFree();
|
remain.markAsFree();
|
||||||
return remaining;
|
return remain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Absorb a free block's storage into this (adjacent previous) free block. */
|
/* Absorb a free block's storage into this (adjacent previous) free block. */
|
||||||
absorb(block: BlockHeader): void {
|
absorb(block: BlockHeader): void {
|
||||||
assert(!this.isLast, "previous block can't be last");
|
assert(!this.isLast,
|
||||||
this.tagged_size += block.size + block_header_overhead; // Leaves flags untouched.
|
"previous block can't be last"
|
||||||
|
);
|
||||||
|
// Leaves tags untouched
|
||||||
|
this.tagged_size += block.size + BlockHeader.OVERHEAD;
|
||||||
this.linkNext();
|
this.linkNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeof_block_header_t: usize = 4 * sizeof<usize>();
|
|
||||||
|
|
||||||
// Since block sizes are always at least a multiple of 4, the two least
|
|
||||||
// significant bits of the size field are used to store the block status.
|
|
||||||
const block_header_free_bit: usize = 1 << 0;
|
|
||||||
const block_header_prev_free_bit: usize = 1 << 1;
|
|
||||||
|
|
||||||
// The size of the block header exposed to used blocks is the size field.
|
|
||||||
// The prev_phys_block field is stored *inside* the previous free block.
|
|
||||||
const block_header_overhead: usize = sizeof<usize>();
|
|
||||||
|
|
||||||
// User data starts directly after the size field in a used block.
|
|
||||||
const block_start_offset: usize = sizeof<usize>() + sizeof<usize>();
|
|
||||||
|
|
||||||
// A free block must be large enough to store its header minus the size of
|
|
||||||
// the prev_phys_block field, and no larger than the number of addressable
|
|
||||||
// bits for FL_INDEX.
|
|
||||||
const block_size_min: usize = sizeof_block_header_t - sizeof<usize>();
|
|
||||||
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 here, indicating free
|
||||||
|
|
||||||
|
// The control structure uses 3188 bytes in WASM32.
|
||||||
|
static readonly SIZE: usize = (
|
||||||
|
BlockHeader.SIZE
|
||||||
|
+ (1 + FL_INDEX_COUNT) * sizeof<u32>()
|
||||||
|
+ FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof<usize>()
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////// Fields ////////////////////////////////////
|
///////////////////////////////// Fields ////////////////////////////////////
|
||||||
|
|
||||||
/* First level free list bitmap. */
|
/** First level free list bitmap. */
|
||||||
fl_bitmap: u32;
|
fl_bitmap: u32;
|
||||||
|
|
||||||
/** Gets the second level free list bitmap for the specified index. Equivalent to `sl_bitmap[fl_index]`. */
|
/**
|
||||||
|
* 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 = BlockHeader.SIZE + 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`. */
|
/**
|
||||||
|
* 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 = BlockHeader.SIZE + 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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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 {
|
* Gets the head of the free list for the specified indexes.
|
||||||
const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
|
* Equivalent to `blocks[fl_index][sl_index]`.
|
||||||
return load<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>());
|
*/
|
||||||
|
blocks(fli: u32, sli: u32): BlockHeader {
|
||||||
|
const offset = BlockHeader.SIZE + (1 + FL_INDEX_COUNT) * sizeof<u32>();
|
||||||
|
return load<BlockHeader>(
|
||||||
|
changetype<usize>(this)
|
||||||
|
+ offset
|
||||||
|
+ (fli * SL_INDEX_COUNT + sli) * sizeof<usize>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the head of the free list for the specified indexes. Equivalent to `blocks[fl_index][sl_index] = block`. */
|
/**
|
||||||
|
* 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 = BlockHeader.SIZE + (1 + 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 ///////////////////////////////////
|
///////////////////////////////// Methods ///////////////////////////////////
|
||||||
@ -246,14 +292,18 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
/* Inserts a free block into the free block list. */
|
/* Inserts a free block into the free block list. */
|
||||||
insertFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
|
insertFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
|
||||||
var current = this.blocks(fl, sl);
|
var current = this.blocks(fl, sl);
|
||||||
assert(current, "free list cannot have a null entry");
|
assert(current,
|
||||||
assert(block, "cannot insert a null entry into the free list");
|
"free list cannot have a null entry"
|
||||||
|
);
|
||||||
|
assert(block,
|
||||||
|
"cannot insert a null entry into the free list"
|
||||||
|
);
|
||||||
block.next_free = current;
|
block.next_free = current;
|
||||||
block.prev_free = this;
|
block.prev_free = this;
|
||||||
current.prev_free = block;
|
current.prev_free = block;
|
||||||
|
assert(block.toDataPtr() == align_ptr(block.toDataPtr(), ALIGN_SIZE),
|
||||||
assert(block.toPayloadPtr() == align_ptr(block.toPayloadPtr(), ALIGN_SIZE), "block not aligned properly");
|
"block not aligned properly"
|
||||||
|
);
|
||||||
// Insert the new block at the head of the list, and mark the first-
|
// Insert the new block at the head of the list, and mark the first-
|
||||||
// and second-level bitmaps appropriately.
|
// and second-level bitmaps appropriately.
|
||||||
this.blocks_set(fl, sl, block);
|
this.blocks_set(fl, sl, block);
|
||||||
@ -265,8 +315,12 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
removeFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
|
removeFreeBlock(block: BlockHeader, fl: i32, sl: i32): void {
|
||||||
var prev = block.prev_free;
|
var prev = block.prev_free;
|
||||||
var next = block.next_free;
|
var next = block.next_free;
|
||||||
assert(prev, "prev_free field can not be null");
|
assert(prev,
|
||||||
assert(next, "next_free field can not be null");
|
"prev_free field cannot be null"
|
||||||
|
);
|
||||||
|
assert(next,
|
||||||
|
"next_free field cannot be null"
|
||||||
|
);
|
||||||
next.prev_free = prev;
|
next.prev_free = prev;
|
||||||
prev.next_free = next;
|
prev.next_free = next;
|
||||||
if (this.blocks(fl, sl) == block) {
|
if (this.blocks(fl, sl) == block) {
|
||||||
@ -284,8 +338,12 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
mergePrevBlock(block: BlockHeader): BlockHeader {
|
mergePrevBlock(block: BlockHeader): BlockHeader {
|
||||||
if (block.isPrevFree) {
|
if (block.isPrevFree) {
|
||||||
var prev = block.prev;
|
var prev = block.prev;
|
||||||
assert(prev, "prev physical block can't be null");
|
assert(prev,
|
||||||
assert(prev.isFree, "prev block is not free though marked as such");
|
"prev physical block can't be null"
|
||||||
|
);
|
||||||
|
assert(prev.isFree,
|
||||||
|
"prev block is not free though marked as such"
|
||||||
|
);
|
||||||
this.removeBlock(prev);
|
this.removeBlock(prev);
|
||||||
prev.absorb(block);
|
prev.absorb(block);
|
||||||
block = prev;
|
block = prev;
|
||||||
@ -296,33 +354,46 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
/** Merges a just-freed block with an adjacent free block. */
|
/** Merges a just-freed block with an adjacent free block. */
|
||||||
mergeNextBlock(block: BlockHeader): BlockHeader {
|
mergeNextBlock(block: BlockHeader): BlockHeader {
|
||||||
var next = block.next;
|
var next = block.next;
|
||||||
assert(next, "next physical block can't be null");
|
assert(next,
|
||||||
|
"next physical block can't be null"
|
||||||
|
);
|
||||||
if (next.isFree) {
|
if (next.isFree) {
|
||||||
assert(!block.isLast, "previous block can't be last");
|
assert(!block.isLast,
|
||||||
|
"previous block can't be last"
|
||||||
|
);
|
||||||
this.removeBlock(next);
|
this.removeBlock(next);
|
||||||
block.absorb(next);
|
block.absorb(next);
|
||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Trims any trailing block space off the end of a block and returns it to the pool. */
|
/**
|
||||||
|
* Trims any trailing block space off the end of a block and returns it to
|
||||||
|
* the pool. */
|
||||||
trimFreeBlock(block: BlockHeader, size: usize): void {
|
trimFreeBlock(block: BlockHeader, size: usize): void {
|
||||||
assert(block.isFree, "block must be free");
|
assert(block.isFree,
|
||||||
|
"block must be free"
|
||||||
|
);
|
||||||
if (block.canSplit(size)) {
|
if (block.canSplit(size)) {
|
||||||
var remaining_block = block.split(size);
|
var remaining_block = block.split(size);
|
||||||
block.linkNext();
|
block.linkNext();
|
||||||
remaining_block.setPrevFree();
|
remaining_block.tagPrevFree();
|
||||||
this.insertBlock(remaining_block);
|
this.insertBlock(remaining_block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Trims any trailing block space off the end of a used block and returns it to the pool. */
|
/**
|
||||||
|
* Trims any trailing block space off the end of a used block and returns it
|
||||||
|
* to the pool.
|
||||||
|
*/
|
||||||
trimUsedBlock(block: BlockHeader, size: usize): void {
|
trimUsedBlock(block: BlockHeader, size: usize): void {
|
||||||
assert(!block.isFree, "block must be used");
|
assert(!block.isFree,
|
||||||
|
"block must be used"
|
||||||
|
);
|
||||||
if (block.canSplit(size)) {
|
if (block.canSplit(size)) {
|
||||||
// If the next block is free, we must coalesce.
|
// If the next block is free, we must coalesce.
|
||||||
var remaining_block = block.split(size);
|
var remaining_block = block.split(size);
|
||||||
remaining_block.setPrevUsed();
|
remaining_block.tagPrevUsed();
|
||||||
remaining_block = this.mergeNextBlock(remaining_block);
|
remaining_block = this.mergeNextBlock(remaining_block);
|
||||||
this.insertBlock(remaining_block);
|
this.insertBlock(remaining_block);
|
||||||
}
|
}
|
||||||
@ -331,8 +402,8 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
trimFreeBlockLeading(block: BlockHeader, size: usize): BlockHeader {
|
trimFreeBlockLeading(block: BlockHeader, size: usize): BlockHeader {
|
||||||
var remaining_block = block;
|
var remaining_block = block;
|
||||||
if (block.canSplit(size)) {
|
if (block.canSplit(size)) {
|
||||||
remaining_block = block.split(size - block_header_overhead);
|
remaining_block = block.split(size - BlockHeader.OVERHEAD);
|
||||||
remaining_block.setPrevFree();
|
remaining_block.tagPrevFree();
|
||||||
block.linkNext();
|
block.linkNext();
|
||||||
this.insertBlock(block);
|
this.insertBlock(block);
|
||||||
}
|
}
|
||||||
@ -340,8 +411,7 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
}
|
}
|
||||||
|
|
||||||
locateFreeBlock(size: usize): BlockHeader {
|
locateFreeBlock(size: usize): BlockHeader {
|
||||||
var index: u64 = 0;
|
var block = changetype<BlockHeader>(0);
|
||||||
var block: BlockHeader = changetype<BlockHeader>(0);
|
|
||||||
if (size) {
|
if (size) {
|
||||||
mapping_search(size);
|
mapping_search(size);
|
||||||
if (fl_out < FL_INDEX_MAX) {
|
if (fl_out < FL_INDEX_MAX) {
|
||||||
@ -358,15 +428,20 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
prepareUsedBlock(block: BlockHeader, size: usize): usize {
|
prepareUsedBlock(block: BlockHeader, size: usize): usize {
|
||||||
var ptr: usize = 0;
|
var ptr: usize = 0;
|
||||||
if (block) {
|
if (block) {
|
||||||
assert(size, "size must be non-zero");
|
assert(size,
|
||||||
|
"size must be non-zero"
|
||||||
|
);
|
||||||
this.trimFreeBlock(block, size);
|
this.trimFreeBlock(block, size);
|
||||||
block.markAsUsed();
|
block.markAsUsed();
|
||||||
ptr = block.toPayloadPtr();
|
ptr = block.toDataPtr();
|
||||||
}
|
}
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a TLSF control structure at the specified memory address, providing the specified number of bytes. */
|
/**
|
||||||
|
* Creates a TLSF control structure at the specified memory address,
|
||||||
|
* providing the specified number of bytes.
|
||||||
|
*/
|
||||||
static create(mem: usize, bytes: usize): Control {
|
static create(mem: usize, bytes: usize): Control {
|
||||||
if ((mem % ALIGN_SIZE) != 0)
|
if ((mem % ALIGN_SIZE) != 0)
|
||||||
throw new RangeError("Memory must be aligned");
|
throw new RangeError("Memory must be aligned");
|
||||||
@ -384,59 +459,61 @@ class Control extends BlockHeader { // Empty lists point at this block to indica
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the initial memory pool
|
// Add the initial memory pool
|
||||||
control.addPool(mem + sizeof_control_t, bytes - sizeof_control_t);
|
control.addPool(mem + Control.SIZE, bytes - Control.SIZE);
|
||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds a pool of free memory. */
|
/** Adds a pool of free memory. */
|
||||||
addPool(mem: usize, bytes: usize): void {
|
addPool(mem: usize, bytes: usize): void {
|
||||||
var block: BlockHeader;
|
|
||||||
var next: BlockHeader;
|
|
||||||
|
|
||||||
// Overhead of the TLSF structures in a given memory block, equal
|
// Overhead of the TLSF structures in a given memory block, equal
|
||||||
// to the overhead of the free block and the sentinel block.
|
// to the overhead of the free block and the sentinel block.
|
||||||
const pool_overhead: usize = 2 * block_header_overhead;
|
const pool_overhead = BlockHeader.OVERHEAD * 2;
|
||||||
|
|
||||||
var pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
|
var pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
|
||||||
if ((mem % ALIGN_SIZE) != 0)
|
if ((mem % ALIGN_SIZE) != 0)
|
||||||
throw new RangeError("Memory must be aligned");
|
throw new RangeError("Memory must be aligned");
|
||||||
if (pool_bytes < block_size_min || pool_bytes > block_size_max)
|
if (pool_bytes < BlockHeader.BLOCK_SIZE_MIN ||
|
||||||
|
pool_bytes > BlockHeader.BLOCK_SIZE_MAX)
|
||||||
throw new RangeError("Memory size must be between min and max");
|
throw new RangeError("Memory size must be between min and max");
|
||||||
|
|
||||||
// Create the main free block. Offset the start of the block slightly
|
// Create the main free block. Offset the start of the block slightly
|
||||||
// so that the prev_phys_block field falls outside of the pool -
|
// so that the prev_phys_block field falls outside of the pool -
|
||||||
// it will never be used.
|
// it will never be used.
|
||||||
block = BlockHeader.fromOffset(mem, -block_header_overhead);
|
var block = BlockHeader.fromOffset(mem, -BlockHeader.OVERHEAD);
|
||||||
block.size = pool_bytes;
|
block.size = pool_bytes;
|
||||||
block.setFree();
|
block.tagFree();
|
||||||
block.setPrevUsed();
|
block.tagPrevUsed();
|
||||||
this.insertBlock(block);
|
this.insertBlock(block);
|
||||||
|
|
||||||
// Split the block to create a zero-size sentinel block.
|
// Split the block to create a zero-size sentinel block.
|
||||||
next = block.linkNext();
|
var next = block.linkNext();
|
||||||
next.size = 0;
|
next.size = 0;
|
||||||
next.setUsed();
|
next.tagUsed();
|
||||||
next.setPrevFree();
|
next.tagPrevFree();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof<u32>() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof<usize>();
|
|
||||||
|
|
||||||
// Alignment helpers
|
// Alignment helpers
|
||||||
|
|
||||||
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"
|
||||||
|
);
|
||||||
return (x + (align - 1)) & ~(align - 1);
|
return (x + (align - 1)) & ~(align - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function align_down(x: usize, align: usize): usize {
|
function align_down(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"
|
||||||
|
);
|
||||||
return x - (x & (align - 1));
|
return x - (x & (align - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
function align_ptr(ptr: usize, align: usize): usize {
|
function align_ptr(ptr: usize, align: usize): usize {
|
||||||
var aligned = (ptr + (align - 1)) & ~(align - 1);
|
var aligned = (ptr + (align - 1)) & ~(align - 1);
|
||||||
assert(!(align & (align - 1)), "must align to a power of two");
|
assert(!(align & (align - 1)),
|
||||||
|
"must align to a power of two"
|
||||||
|
);
|
||||||
return aligned;
|
return aligned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,9 +523,9 @@ function align_ptr(ptr: usize, align: usize): usize {
|
|||||||
*/
|
*/
|
||||||
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;
|
||||||
if (size && size < block_size_max) {
|
if (size && size < BlockHeader.BLOCK_SIZE_MAX) {
|
||||||
var aligned = align_up(size, align);
|
var aligned = align_up(size, align);
|
||||||
adjust = max(aligned, block_size_min);
|
adjust = max(aligned, BlockHeader.BLOCK_SIZE_MIN);
|
||||||
}
|
}
|
||||||
return adjust;
|
return adjust;
|
||||||
}
|
}
|
||||||
@ -456,6 +533,14 @@ function adjust_request_size(size: usize, align: usize): usize {
|
|||||||
// TLSF utility functions. In most cases, these are direct translations of the
|
// TLSF utility functions. In most cases, these are direct translations of the
|
||||||
// documentation found in the white paper.
|
// documentation found in the white paper.
|
||||||
|
|
||||||
|
function ffs<T>(word: i32): i32 {
|
||||||
|
return word ? <i32>ctz(word) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fls<T>(word: T): i32 {
|
||||||
|
return (<i32>sizeof<T>() << 3) - 1 - <i32>clz(word);
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@ -465,18 +550,16 @@ function mapping_insert(size: usize): void {
|
|||||||
sl = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
|
sl = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
|
||||||
} else {
|
} else {
|
||||||
fl = fls<usize>(size);
|
fl = fls<usize>(size);
|
||||||
sl = (<i32>size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2);
|
sl = (<i32>size >> (fl - SL_INDEX_COUNT_LOG2)) ^ 1 << SL_INDEX_COUNT_LOG2;
|
||||||
fl -= (FL_INDEX_SHIFT - 1);
|
fl -= FL_INDEX_SHIFT - 1;
|
||||||
}
|
}
|
||||||
fl_out = fl;
|
fl_out = fl;
|
||||||
sl_out = sl;
|
sl_out = sl;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapping_search(size: usize): void {
|
function mapping_search(size: usize): void {
|
||||||
if (size >= SMALL_BLOCK_SIZE) {
|
if (size >= SMALL_BLOCK_SIZE)
|
||||||
var round: usize = (<usize>1 << (fls<usize>(size) - SL_INDEX_COUNT_LOG2)) - 1;
|
size += (1 << (fls<usize>(size) - SL_INDEX_COUNT_LOG2)) - 1;
|
||||||
size += round;
|
|
||||||
}
|
|
||||||
mapping_insert(size);
|
mapping_insert(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,12 +571,14 @@ function find_suitable_block(control: Control, fl: i32, sl: i32): BlockHeader {
|
|||||||
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)
|
||||||
return changetype<BlockHeader>(0); // Memory pool has been exhausted
|
return changetype<BlockHeader>(0); // Memory pool has been exhausted
|
||||||
fl = ffs<u32>(fl_map);
|
fl = ctz<u32>(fl_map); // ^= ffs<u32>(fl_map) with fl_map != 0
|
||||||
fl_out = fl;
|
fl_out = fl;
|
||||||
sl_map = control.sl_bitmap(fl);
|
sl_map = control.sl_bitmap(fl);
|
||||||
}
|
}
|
||||||
assert(sl_map, "internal error - second level bitmap is null");
|
assert(sl_map,
|
||||||
sl = ffs<u32>(sl_map);
|
"second level bitmap is null"
|
||||||
|
);
|
||||||
|
sl = ctz<u32>(sl_map); // ^= ffs<u32>(sl_map) with sl_map != 0
|
||||||
sl_out = sl;
|
sl_out = sl;
|
||||||
return control.blocks(fl, sl); // First block in the free list
|
return control.blocks(fl, sl); // First block in the free list
|
||||||
}
|
}
|
||||||
@ -514,9 +599,9 @@ function request_memory(size: usize): void {
|
|||||||
TLSF.addPool(<usize>prev_pages << 16, <usize>(next_pages - prev_pages) << 16);
|
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. */
|
/** Allocates a chunk of memory of the given size and returns its address. */
|
||||||
export function allocate_memory(size: usize): usize {
|
export function allocate_memory(size: usize): usize {
|
||||||
if (!TLSF) // Initialize TLSF when actually used so it DCEs just fine otherwise
|
if (!TLSF) // Initialize when actually used so it DCEs just fine otherwise
|
||||||
TLSF = Control.create(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 adjust = adjust_request_size(size, ALIGN_SIZE);
|
var adjust = adjust_request_size(size, ALIGN_SIZE);
|
||||||
@ -532,8 +617,10 @@ export function allocate_memory(size: usize): usize {
|
|||||||
export function free_memory(ptr: usize): void {
|
export function free_memory(ptr: usize): void {
|
||||||
if (TLSF && ptr) {
|
if (TLSF && ptr) {
|
||||||
var control = changetype<Control>(TLSF);
|
var control = changetype<Control>(TLSF);
|
||||||
var block = BlockHeader.fromPayloadPtr(ptr);
|
var block = BlockHeader.fromDataPtr(ptr);
|
||||||
assert(!block.isFree, "block already marked as free");
|
assert(!block.isFree,
|
||||||
|
"block already marked as free"
|
||||||
|
);
|
||||||
block.markAsFree();
|
block.markAsFree();
|
||||||
block = control.mergePrevBlock(block);
|
block = control.mergePrevBlock(block);
|
||||||
block = control.mergeNextBlock(block);
|
block = control.mergeNextBlock(block);
|
||||||
@ -543,9 +630,15 @@ export function free_memory(ptr: usize): void {
|
|||||||
|
|
||||||
// Extra debugging
|
// Extra debugging
|
||||||
|
|
||||||
assert(sizeof<u32>() * 8 >= SL_INDEX_COUNT, "SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type");
|
assert(sizeof<u32>() << 3 >= SL_INDEX_COUNT,
|
||||||
assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT, "invalid alignment");
|
"SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type"
|
||||||
assert(test_ffs_fls() == 0, "ffs/fls are not working properly");
|
);
|
||||||
|
assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT,
|
||||||
|
"invalid alignment"
|
||||||
|
);
|
||||||
|
assert(test_ffs_fls() == 0,
|
||||||
|
"ffs/fls are not working properly"
|
||||||
|
);
|
||||||
|
|
||||||
function test_ffs_fls(): i32 {
|
function test_ffs_fls(): i32 {
|
||||||
var rv = 0;
|
var rv = 0;
|
||||||
@ -575,31 +668,41 @@ function check(): i32 {
|
|||||||
var sl_map = sl_list & (1 << j);
|
var sl_map = sl_list & (1 << j);
|
||||||
var block = control.blocks(i, j);
|
var block = control.blocks(i, j);
|
||||||
if (!fl_map) {
|
if (!fl_map) {
|
||||||
if (!assert(!sl_map, "second-level map must be null"))
|
if (!assert(!sl_map,
|
||||||
--status;
|
"second-level map must be null")
|
||||||
|
) --status;
|
||||||
}
|
}
|
||||||
if (!sl_map) {
|
if (!sl_map) {
|
||||||
if (!assert(block == control, "block list must be null"))
|
if (!assert(block == control,
|
||||||
--status;
|
"block list must be null")
|
||||||
|
) --status;
|
||||||
} else {
|
} else {
|
||||||
if (!assert(sl_list, "no free blocks in second-level map"))
|
if (!assert(sl_list,
|
||||||
--status;
|
"no free blocks in second-level map")
|
||||||
if (!assert(block != control, "block should not be null"))
|
) --status;
|
||||||
--status;
|
if (!assert(block != control,
|
||||||
|
"block should not be null")
|
||||||
|
) --status;
|
||||||
while (block != control) {
|
while (block != control) {
|
||||||
if (!assert(block.isFree, "block should be free"))
|
if (!assert(block.isFree,
|
||||||
--status;
|
"block should be free")
|
||||||
if (!assert(!block.isPrevFree, "blocks should have coalesced"))
|
) --status;
|
||||||
--status;
|
if (!assert(!block.isPrevFree,
|
||||||
if (!assert(!block.next.isFree, "blocks should have coalesced"))
|
"blocks should have coalesced")
|
||||||
--status;
|
) --status;
|
||||||
if (!assert(block.next.isPrevFree, "block should be free"))
|
if (!assert(!block.next.isFree,
|
||||||
--status;
|
"blocks should have coalesced")
|
||||||
if (!assert(block.size >= block_size_min, "block not minimum size"))
|
) --status;
|
||||||
--status;
|
if (!assert(block.next.isPrevFree,
|
||||||
|
"block should be free")
|
||||||
|
) --status;
|
||||||
|
if (!assert(block.size >= BlockHeader.BLOCK_SIZE_MIN,
|
||||||
|
"block < minimum size")
|
||||||
|
) --status;
|
||||||
mapping_insert(block.size);
|
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,
|
||||||
--status;
|
"block size indexed in wrong list")
|
||||||
|
) --status;
|
||||||
block = block.next_free;
|
block = block.next_free;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -612,30 +715,32 @@ var integrity_prev_status: i32;
|
|||||||
var integrity_status: i32;
|
var integrity_status: i32;
|
||||||
|
|
||||||
function integrity_walker(ptr: usize, size: usize, used: bool): void {
|
function integrity_walker(ptr: usize, size: usize, used: bool): void {
|
||||||
var block = BlockHeader.fromPayloadPtr(ptr);
|
var block = BlockHeader.fromDataPtr(ptr);
|
||||||
var this_prev_status = block.isPrevFree;
|
var this_prev_status = block.isPrevFree;
|
||||||
var this_status = block.isFree;
|
var this_status = block.isFree;
|
||||||
var this_block_size = block.size;
|
var this_block_size = block.size;
|
||||||
|
|
||||||
var status = 0;
|
var status = 0;
|
||||||
if (!assert(integrity_prev_status == this_prev_status, "prev status incorrect"))
|
if (!assert(integrity_prev_status == this_prev_status,
|
||||||
--status;
|
"prev status incorrect")
|
||||||
if (!assert(size == this_block_size, "block size incorrect"))
|
) --status;
|
||||||
--status;
|
if (!assert(size == this_block_size,
|
||||||
|
"block size incorrect")
|
||||||
|
) --status;
|
||||||
integrity_prev_status = this_status;
|
integrity_prev_status = this_status;
|
||||||
integrity_status += status;
|
integrity_status += status;
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_pool(pool: usize): i32 {
|
function check_pool(pool: usize): i32 {
|
||||||
if (pool < 0x10000) { // first pool
|
if (pool < 0x10000) { // first pool
|
||||||
pool = changetype<usize>(TLSF) + sizeof_control_t;
|
pool = changetype<usize>(TLSF) + Control.SIZE;
|
||||||
}
|
}
|
||||||
// inlined walk_bool with static integrity_walker
|
// inlined walk_bool with static integrity_walker
|
||||||
integrity_prev_status = integrity_status = 0;
|
integrity_prev_status = integrity_status = 0;
|
||||||
var block = BlockHeader.fromOffset(pool, -block_header_overhead);
|
var block = BlockHeader.fromOffset(pool, -BlockHeader.OVERHEAD);
|
||||||
while (block && !block.isLast) {
|
while (block && !block.isLast) {
|
||||||
integrity_walker(
|
integrity_walker(
|
||||||
block.toPayloadPtr(),
|
block.toDataPtr(),
|
||||||
block.size,
|
block.size,
|
||||||
!block.isFree
|
!block.isFree
|
||||||
);
|
);
|
||||||
@ -644,4 +749,4 @@ function check_pool(pool: usize): i32 {
|
|||||||
return integrity_status;
|
return integrity_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export { check, check_pool, set_memory };
|
// export { check, check_pool }; // Uncomment to enable in tests/index.js
|
||||||
|
@ -416,27 +416,37 @@ export class Compiler extends DiagnosticEmitter {
|
|||||||
if (!this.module.noEmit)
|
if (!this.module.noEmit)
|
||||||
this.startFunctionBody.push(setExpr);
|
this.startFunctionBody.push(setExpr);
|
||||||
} else {
|
} else {
|
||||||
this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr);
|
// TODO: not necessary to create a global if constant and not a file-level export anyway
|
||||||
if (!global.isMutable && !this.module.noEmit) {
|
if (!global.isMutable) {
|
||||||
var exprType = _BinaryenExpressionGetType(initExpr);
|
if (!this.module.noEmit) {
|
||||||
switch (exprType) {
|
var exprType = _BinaryenExpressionGetType(initExpr);
|
||||||
case NativeType.I32:
|
switch (exprType) {
|
||||||
global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initExpr), 0);
|
|
||||||
break;
|
case NativeType.I32:
|
||||||
case NativeType.I64:
|
global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initExpr), 0);
|
||||||
global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr));
|
break;
|
||||||
break;
|
|
||||||
case NativeType.F32:
|
case NativeType.I64:
|
||||||
global.constantFloatValue = _BinaryenConstGetValueF32(initExpr);
|
global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr));
|
||||||
break;
|
break;
|
||||||
case NativeType.F64:
|
|
||||||
global.constantFloatValue = _BinaryenConstGetValueF64(initExpr);
|
case NativeType.F32:
|
||||||
break;
|
global.constantFloatValue = _BinaryenConstGetValueF32(initExpr);
|
||||||
default:
|
break;
|
||||||
throw new Error("concrete type expected");
|
|
||||||
|
case NativeType.F64:
|
||||||
|
global.constantFloatValue = _BinaryenConstGetValueF64(initExpr);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("concrete type expected");
|
||||||
|
}
|
||||||
|
global.hasConstantValue = true;
|
||||||
|
if (!declaration || isModuleExport(global, declaration))
|
||||||
|
this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr);
|
||||||
}
|
}
|
||||||
global.hasConstantValue = true;
|
} else if (!this.module.noEmit)
|
||||||
}
|
this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr);
|
||||||
}
|
}
|
||||||
global.isCompiled = true;
|
global.isCompiled = true;
|
||||||
return true;
|
return true;
|
||||||
|
@ -1436,6 +1436,7 @@ export class Global extends VariableLikeElement {
|
|||||||
case ModifierKind.EXPORT: this.isExported = true; break;
|
case ModifierKind.EXPORT: this.isExported = true; break;
|
||||||
case ModifierKind.CONST: this.isConstant = true; break;
|
case ModifierKind.CONST: this.isConstant = true; break;
|
||||||
case ModifierKind.DECLARE: this.isDeclared = true; break;
|
case ModifierKind.DECLARE: this.isDeclared = true; break;
|
||||||
|
case ModifierKind.READONLY: this.isConstant = true; break;
|
||||||
case ModifierKind.STATIC: break; // static fields become globals
|
case ModifierKind.STATIC: break; // static fields become globals
|
||||||
default: throw new Error("unexpected modifier");
|
default: throw new Error("unexpected modifier");
|
||||||
}
|
}
|
||||||
|
20
tests/binaryen/precompute-join.wast
Normal file
20
tests/binaryen/precompute-join.wast
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
(module
|
||||||
|
(type $ii (func (param i32) (result i32)))
|
||||||
|
(export "fls" (func $assembly/tlsf/fls<usize>))
|
||||||
|
(func $assembly/tlsf/fls<usize> (; 8 ;) (type $ii) (param $0 i32) (result i32)
|
||||||
|
(return
|
||||||
|
(i32.sub
|
||||||
|
(i32.sub
|
||||||
|
(i32.shl
|
||||||
|
(i32.const 4)
|
||||||
|
(i32.const 3)
|
||||||
|
)
|
||||||
|
(i32.clz
|
||||||
|
(get_local $0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(i32.const 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user