diff --git a/examples/tlsf/assembly/tlsf.ts b/examples/tlsf/assembly/tlsf.ts index 53bdb7c8..c9eaf9df 100644 --- a/examples/tlsf/assembly/tlsf.ts +++ b/examples/tlsf/assembly/tlsf.ts @@ -1,5 +1,5 @@ ////////////// 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 @@ -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 SMALL_BLOCK_SIZE: u32 = 1 << FL_INDEX_SHIFT; -// WebAssembly-specific ffs/fls - -function ffs(word: i32): i32 { - return word ? ctz(word) : -1; -} - -function fls(word: T): i32 { - return (sizeof() << 3) - clz(word) - 1; -} - /** Block header structure. */ @explicit class BlockHeader { + /////////////////////////////// Constants /////////////////////////////////// + + // One block header is 16 bytes in WASM32 and 32 bytes in WASM64. + static readonly SIZE: usize = 4 * sizeof(); + + // 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(); + + // User data starts directly after the size field in a used block. + static readonly USERDATA_OFFSET: usize = sizeof() + sizeof(); + + // 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(); + static readonly BLOCK_SIZE_MAX: usize = 1 << FL_INDEX_MAX; + ///////////////////////////////// Fields //////////////////////////////////// /** @@ -54,12 +67,14 @@ class BlockHeader { /** 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); + const tags = BlockHeader.FREE_BIT | BlockHeader.PREV_FREE_BIT; + return this.tagged_size & ~tags; } /** 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)); + const tags = BlockHeader.FREE_BIT | BlockHeader.PREV_FREE_BIT; + this.tagged_size = size | (this.tagged_size & tags); } /** Tests if this is the last block. */ @@ -69,42 +84,42 @@ class BlockHeader { /** Tests if this block's status is 'free'. */ 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'. */ - setFree(): void { - this.tagged_size |= block_header_free_bit; + /** Tags this block as 'free'. Careful: Does not update adjacent blocks. */ + tagFree(): void { + this.tagged_size |= BlockHeader.FREE_BIT; } - /** Sets this block's status to 'used'. */ - setUsed(): void { - this.tagged_size &= ~block_header_free_bit; + /** Tags this block as 'used'. Careful: Does not update adjacent blocks. */ + tagUsed(): void { + this.tagged_size &= ~BlockHeader.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; + return (this.tagged_size & BlockHeader.PREV_FREE_BIT) != 0; } - /** Sets the previous block's status to 'free'. */ - setPrevFree(): void { - this.tagged_size |= block_header_prev_free_bit; + /** Tags this block as 'prev is free'. Does not update adjacent blocks. */ + tagPrevFree(): void { + this.tagged_size |= BlockHeader.PREV_FREE_BIT; } - /** Sets the previous block's status to 'used'. */ - setPrevUsed(): void { - this.tagged_size &= ~block_header_prev_free_bit; + /** Tags this block as 'prev is used'. Does not update adjacent blocks. */ + tagPrevUsed(): void { + this.tagged_size &= ~BlockHeader.PREV_FREE_BIT; } - /** Gets the block header matching the specified payload pointer. */ - static fromPayloadPtr(ptr: usize): BlockHeader { - return changetype(ptr - block_start_offset); + /** Gets the block header matching the specified data pointer. */ + static fromDataPtr(ptr: usize): BlockHeader { + return changetype(ptr - BlockHeader.USERDATA_OFFSET); } - /** Returns the address of this block's payload. */ - toPayloadPtr(): usize { - return changetype(this) + block_start_offset; + /** Returns the address of this block's data. */ + toDataPtr(): usize { + return changetype(this) + BlockHeader.USERDATA_OFFSET; } /** Gets the next block after this one using the specified size. */ @@ -114,14 +129,21 @@ class BlockHeader { /** Gets the previous block. */ get prev(): BlockHeader { - assert(this.isPrevFree, "previous block must be free"); + 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); + assert(!this.isLast, + "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'. */ markAsFree(): void { var next = this.linkNext(); // Link the block to the next block, first. - next.setPrevFree(); - this.setFree(); + next.tagPrevFree(); + this.tagFree(); } /** Marks this block as being 'used'. */ markAsUsed(): void { var next = this.next; - next.setPrevUsed(); - this.setUsed(); + next.tagPrevUsed(); + this.tagUsed(); } /** Tests if this block can be splitted. */ 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. */ 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"); - + var remain = BlockHeader.fromOffset( + this.toDataPtr(), + size - BlockHeader.OVERHEAD + ); + var remain_size = this.size - (size + BlockHeader.OVERHEAD); + assert(remain.toDataPtr() == align_ptr(remain.toDataPtr(), ALIGN_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; - remaining.markAsFree(); - return remaining; + remain.markAsFree(); + return remain; } /* 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. + assert(!this.isLast, + "previous block can't be last" + ); + // Leaves tags untouched + this.tagged_size += block.size + BlockHeader.OVERHEAD; this.linkNext(); } } -const sizeof_block_header_t: usize = 4 * sizeof(); - -// 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(); - -// User data starts directly after the size field in a used block. -const block_start_offset: usize = sizeof() + sizeof(); - -// 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(); -const block_size_max: usize = 1 << FL_INDEX_MAX; - /* The TLSF control structure. */ @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() + + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof() + ); ///////////////////////////////// Fields //////////////////////////////////// - /* First level free list bitmap. */ + /** First level free list bitmap. */ 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 { - const offset: usize = sizeof_block_header_t + sizeof(); - return load(changetype(this) + offset + fl_index * sizeof()); + const offset = BlockHeader.SIZE + sizeof(); + return load( + changetype(this) + + offset + + fl_index * sizeof() + ); } - /** 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 { - const offset: usize = sizeof_block_header_t + sizeof(); - return store(changetype(this) + offset + fl_index * sizeof(), sl_map); + const offset = BlockHeader.SIZE + sizeof(); + return store( + changetype(this) + + offset + + fl_index * sizeof(), + 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 { - const offset: usize = sizeof_block_header_t + sizeof() + FL_INDEX_COUNT * sizeof(); - return load(changetype(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof()); + /** + * Gets the head of the free list for the specified indexes. + * Equivalent to `blocks[fl_index][sl_index]`. + */ + blocks(fli: u32, sli: u32): BlockHeader { + const offset = BlockHeader.SIZE + (1 + FL_INDEX_COUNT) * sizeof(); + return load( + changetype(this) + + offset + + (fli * SL_INDEX_COUNT + sli) * sizeof() + ); } - /** 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 { - const offset: usize = sizeof_block_header_t + sizeof() + FL_INDEX_COUNT * sizeof(); - return store(changetype(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof(), block); + const offset = BlockHeader.SIZE + (1 + FL_INDEX_COUNT) * sizeof(); + return store( + changetype(this) + + offset + + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof(), + block + ); } ///////////////////////////////// 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. */ 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"); + 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"); - + assert(block.toDataPtr() == align_ptr(block.toDataPtr(), 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); @@ -265,8 +315,12 @@ class Control extends BlockHeader { // Empty lists point at this block to indica 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"); + assert(prev, + "prev_free field cannot be null" + ); + assert(next, + "next_free field cannot be null" + ); next.prev_free = prev; prev.next_free = next; 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 { 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"); + 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; @@ -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. */ mergeNextBlock(block: BlockHeader): BlockHeader { 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) { - assert(!block.isLast, "previous block can't be last"); + 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. */ + /** + * 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"); + assert(block.isFree, + "block must be free" + ); if (block.canSplit(size)) { var remaining_block = block.split(size); block.linkNext(); - remaining_block.setPrevFree(); + remaining_block.tagPrevFree(); 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 { - assert(!block.isFree, "block must be used"); + 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.tagPrevUsed(); remaining_block = this.mergeNextBlock(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 { var remaining_block = block; if (block.canSplit(size)) { - remaining_block = block.split(size - block_header_overhead); - remaining_block.setPrevFree(); + remaining_block = block.split(size - BlockHeader.OVERHEAD); + remaining_block.tagPrevFree(); block.linkNext(); this.insertBlock(block); } @@ -340,8 +411,7 @@ class Control extends BlockHeader { // Empty lists point at this block to indica } locateFreeBlock(size: usize): BlockHeader { - var index: u64 = 0; - var block: BlockHeader = changetype(0); + var block = changetype(0); if (size) { mapping_search(size); 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 { var ptr: usize = 0; if (block) { - assert(size, "size must be non-zero"); + assert(size, + "size must be non-zero" + ); this.trimFreeBlock(block, size); block.markAsUsed(); - ptr = block.toPayloadPtr(); + ptr = block.toDataPtr(); } 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 { if ((mem % ALIGN_SIZE) != 0) 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 - control.addPool(mem + sizeof_control_t, bytes - sizeof_control_t); + control.addPool(mem + Control.SIZE, bytes - Control.SIZE); 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; + const pool_overhead = BlockHeader.OVERHEAD * 2; 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) + if (pool_bytes < BlockHeader.BLOCK_SIZE_MIN || + pool_bytes > BlockHeader.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); + var block = BlockHeader.fromOffset(mem, -BlockHeader.OVERHEAD); block.size = pool_bytes; - block.setFree(); - block.setPrevUsed(); + block.tagFree(); + block.tagPrevUsed(); this.insertBlock(block); // Split the block to create a zero-size sentinel block. - next = block.linkNext(); + var next = block.linkNext(); next.size = 0; - next.setUsed(); - next.setPrevFree(); + next.tagUsed(); + next.tagPrevFree(); } } -const sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof(); - // Alignment helpers 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); } 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)); } function align_ptr(ptr: usize, align: usize): usize { 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; } @@ -446,9 +523,9 @@ function align_ptr(ptr: usize, align: usize): usize { */ function adjust_request_size(size: usize, align: usize): usize { var adjust: usize = 0; - if (size && size < block_size_max) { + if (size && size < BlockHeader.BLOCK_SIZE_MAX) { var aligned = align_up(size, align); - adjust = max(aligned, block_size_min); + adjust = max(aligned, BlockHeader.BLOCK_SIZE_MIN); } 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 // documentation found in the white paper. +function ffs(word: i32): i32 { + return word ? ctz(word) : -1; +} + +function fls(word: T): i32 { + return (sizeof() << 3) - 1 - clz(word); +} + var fl_out: i32, sl_out: i32; function mapping_insert(size: usize): void { @@ -465,18 +550,16 @@ function mapping_insert(size: usize): void { sl = size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT); } else { fl = fls(size); - sl = (size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2); - fl -= (FL_INDEX_SHIFT - 1); + sl = (size >> (fl - SL_INDEX_COUNT_LOG2)) ^ 1 << SL_INDEX_COUNT_LOG2; + fl -= FL_INDEX_SHIFT - 1; } fl_out = fl; sl_out = sl; } function mapping_search(size: usize): void { - if (size >= SMALL_BLOCK_SIZE) { - var round: usize = (1 << (fls(size) - SL_INDEX_COUNT_LOG2)) - 1; - size += round; - } + if (size >= SMALL_BLOCK_SIZE) + size += (1 << (fls(size) - SL_INDEX_COUNT_LOG2)) - 1; mapping_insert(size); } @@ -488,12 +571,14 @@ function find_suitable_block(control: Control, fl: i32, sl: i32): BlockHeader { var fl_map = control.fl_bitmap & (~0 << (fl + 1)); if (!fl_map) return changetype(0); // Memory pool has been exhausted - fl = ffs(fl_map); + fl = ctz(fl_map); // ^= ffs(fl_map) with fl_map != 0 fl_out = fl; sl_map = control.sl_bitmap(fl); } - assert(sl_map, "internal error - second level bitmap is null"); - sl = ffs(sl_map); + assert(sl_map, + "second level bitmap is null" + ); + sl = ctz(sl_map); // ^= ffs(sl_map) with sl_map != 0 sl_out = sl; return control.blocks(fl, sl); // First block in the free list } @@ -514,9 +599,9 @@ function request_memory(size: usize): void { TLSF.addPool(prev_pages << 16, (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 { - 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); var control = changetype(TLSF); 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 { if (TLSF && ptr) { var control = changetype(TLSF); - var block = BlockHeader.fromPayloadPtr(ptr); - assert(!block.isFree, "block already marked as free"); + var block = BlockHeader.fromDataPtr(ptr); + assert(!block.isFree, + "block already marked as free" + ); block.markAsFree(); block = control.mergePrevBlock(block); block = control.mergeNextBlock(block); @@ -543,9 +630,15 @@ export function free_memory(ptr: usize): void { // Extra debugging -assert(sizeof() * 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"); -assert(test_ffs_fls() == 0, "ffs/fls are not working properly"); +assert(sizeof() << 3 >= 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" +); +assert(test_ffs_fls() == 0, + "ffs/fls are not working properly" +); function test_ffs_fls(): i32 { var rv = 0; @@ -575,31 +668,41 @@ function check(): i32 { var sl_map = sl_list & (1 << j); var block = control.blocks(i, j); if (!fl_map) { - if (!assert(!sl_map, "second-level map must be null")) - --status; + if (!assert(!sl_map, + "second-level map must be null") + ) --status; } if (!sl_map) { - if (!assert(block == control, "block list must be null")) - --status; + if (!assert(block == control, + "block list must be null") + ) --status; } else { - if (!assert(sl_list, "no free blocks in second-level map")) - --status; - if (!assert(block != control, "block should not be null")) - --status; + if (!assert(sl_list, + "no free blocks in second-level map") + ) --status; + if (!assert(block != control, + "block should not be null") + ) --status; while (block != control) { - if (!assert(block.isFree, "block should be free")) - --status; - if (!assert(!block.isPrevFree, "blocks should have coalesced")) - --status; - if (!assert(!block.next.isFree, "blocks should have coalesced")) - --status; - if (!assert(block.next.isPrevFree, "block should be free")) - --status; - if (!assert(block.size >= block_size_min, "block not minimum size")) - --status; + if (!assert(block.isFree, + "block should be free") + ) --status; + if (!assert(!block.isPrevFree, + "blocks should have coalesced") + ) --status; + if (!assert(!block.next.isFree, + "blocks should have coalesced") + ) --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); - if (!assert(fl_out == i && sl_out == j, "block size indexed in wrong list")) - --status; + if (!assert(fl_out == i && sl_out == j, + "block size indexed in wrong list") + ) --status; block = block.next_free; } } @@ -612,30 +715,32 @@ var integrity_prev_status: i32; var integrity_status: i32; 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_status = block.isFree; var this_block_size = block.size; var status = 0; - if (!assert(integrity_prev_status == this_prev_status, "prev status incorrect")) - --status; - if (!assert(size == this_block_size, "block size incorrect")) - --status; + if (!assert(integrity_prev_status == this_prev_status, + "prev status incorrect") + ) --status; + if (!assert(size == this_block_size, + "block size incorrect") + ) --status; integrity_prev_status = this_status; integrity_status += status; } function check_pool(pool: usize): i32 { if (pool < 0x10000) { // first pool - pool = changetype(TLSF) + sizeof_control_t; + pool = changetype(TLSF) + Control.SIZE; } // inlined walk_bool with static integrity_walker 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) { integrity_walker( - block.toPayloadPtr(), + block.toDataPtr(), block.size, !block.isFree ); @@ -644,4 +749,4 @@ function check_pool(pool: usize): i32 { return integrity_status; } -// export { check, check_pool, set_memory }; +// export { check, check_pool }; // Uncomment to enable in tests/index.js diff --git a/src/compiler.ts b/src/compiler.ts index dab7281f..cba39226 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -416,27 +416,37 @@ export class Compiler extends DiagnosticEmitter { if (!this.module.noEmit) this.startFunctionBody.push(setExpr); } else { - this.module.addGlobal(internalName, nativeType, global.isMutable, initExpr); - if (!global.isMutable && !this.module.noEmit) { - var exprType = _BinaryenExpressionGetType(initExpr); - switch (exprType) { - case NativeType.I32: - global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initExpr), 0); - break; - case NativeType.I64: - global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr)); - break; - case NativeType.F32: - global.constantFloatValue = _BinaryenConstGetValueF32(initExpr); - break; - case NativeType.F64: - global.constantFloatValue = _BinaryenConstGetValueF64(initExpr); - break; - default: - throw new Error("concrete type expected"); + // TODO: not necessary to create a global if constant and not a file-level export anyway + if (!global.isMutable) { + if (!this.module.noEmit) { + var exprType = _BinaryenExpressionGetType(initExpr); + switch (exprType) { + + case NativeType.I32: + global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initExpr), 0); + break; + + case NativeType.I64: + global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr)); + break; + + case NativeType.F32: + global.constantFloatValue = _BinaryenConstGetValueF32(initExpr); + break; + + 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; return true; diff --git a/src/program.ts b/src/program.ts index e7c5ba86..691b1032 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1436,6 +1436,7 @@ export class Global extends VariableLikeElement { case ModifierKind.EXPORT: this.isExported = true; break; case ModifierKind.CONST: this.isConstant = true; break; case ModifierKind.DECLARE: this.isDeclared = true; break; + case ModifierKind.READONLY: this.isConstant = true; break; case ModifierKind.STATIC: break; // static fields become globals default: throw new Error("unexpected modifier"); } diff --git a/tests/binaryen/precompute-join.wast b/tests/binaryen/precompute-join.wast new file mode 100644 index 00000000..6850e1dc --- /dev/null +++ b/tests/binaryen/precompute-join.wast @@ -0,0 +1,20 @@ +(module + (type $ii (func (param i32) (result i32))) + (export "fls" (func $assembly/tlsf/fls)) + (func $assembly/tlsf/fls (; 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) + ) + ) + ) +)