From 7d5e56cef53c74e084c0113bb4c33075854cedc5 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Tue, 16 Jan 2018 17:52:48 +0100 Subject: [PATCH] More TLSF --- bin/asc.js | 6 + bin/asc.json | 10 +- examples/tlsf/assembly/LICENSE | 36 +++ examples/tlsf/assembly/tlsf.ts | 406 ++++++++++++++++++--------------- examples/tlsf/package.json | 2 +- examples/tlsf/tests/index.js | 85 ++++--- 6 files changed, 324 insertions(+), 221 deletions(-) create mode 100644 examples/tlsf/assembly/LICENSE diff --git a/bin/asc.js b/bin/asc.js index 6f2378bc..7f150156 100644 --- a/bin/asc.js +++ b/bin/asc.js @@ -167,6 +167,12 @@ else if (args.trapMode !== "allow") { if (args.optimize) module.optimize(); +if (args.runPasses) { + if (typeof args.runPasses === "string") + args.runPasses = args.runPasses.split(","); + module.runPasses(args.runPasses.map(pass => pass.trim())); +} + var hasOutput = false; if (args.outFile != null) { diff --git a/bin/asc.json b/bin/asc.json index 60eafd00..b165abfe 100644 --- a/bin/asc.json +++ b/bin/asc.json @@ -58,11 +58,15 @@ "trapMode": { "desc": [ "Sets the trap mode to use.", - "allow Allow trapping operations. This is the default.", - "clamp Replace trapping operations with clamping semantics.", - "js Replace trapping operations with JS semantics." + " allow Allow trapping operations. This is the default.", + " clamp Replace trapping operations with clamping semantics.", + " js Replace trapping operations with JS semantics." ], "type": "string", "default": "allow" + }, + "runPasses": { + "desc": "Specifies additional Binaryen passes to run.", + "type": "string" } } diff --git a/examples/tlsf/assembly/LICENSE b/examples/tlsf/assembly/LICENSE new file mode 100644 index 00000000..7caf9595 --- /dev/null +++ b/examples/tlsf/assembly/LICENSE @@ -0,0 +1,36 @@ +tlsf.ts is based on: + +Two Level Segregated Fit memory allocator, version 3.1. +Written by Matthew Conte + http://tlsf.baisoku.org + +Based on the original documentation by Miguel Masmano: + http://www.gii.upv.es/tlsf/main/docs + +This implementation was written to the specification +of the document, therefore no GPL restrictions apply. + +Copyright (c) 2006-2016, Matthew Conte +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/tlsf/assembly/tlsf.ts b/examples/tlsf/assembly/tlsf.ts index da1ddaa7..714977f7 100644 --- a/examples/tlsf/assembly/tlsf.ts +++ b/examples/tlsf/assembly/tlsf.ts @@ -1,16 +1,20 @@ -// based on https://github.com/mattconte/tlsf (BSD) +// based on https://github.com/mattconte/tlsf (BSD, see LICENSE file) -type size_t = u32; +// Configuration -// TLSF achieves O(1) cost for malloc and free operations by limiting -// the search for a free block to a free list of guaranteed size -// adequate to fulfill the request, combined with efficient free list -// queries using bitmasks and architecture-specific bit-manipulation -// routines. -// -// NOTE: TLSF spec relies on ffs/fls returning values 0..31 with -1 -// indicating that no bits are set. In WebAssembly, ctz and clz return -// 32/64 if no bits are set. +const SL_INDEX_COUNT_LOG2: u32 = 5; + +// Internal constants + +const ALIGN_SIZE_LOG2: u32 = sizeof() == 8 ? 3 : 2; +const ALIGN_SIZE: u32 = 1 << ALIGN_SIZE_LOG2; +const FL_INDEX_MAX: u32 = sizeof() == 8 ? 32 : 30; +const SL_INDEX_COUNT: u32 = 1 << SL_INDEX_COUNT_LOG2; +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; @@ -20,67 +24,44 @@ function fls(word: T): i32 { return (sizeof() << 3) - clz(word) - 1; } -const SL_INDEX_COUNT_LOG2: u32 = 5; - -const ALIGN_SIZE_LOG2: u32 = sizeof() == 8 ? 3 : 2; -const ALIGN_SIZE: u32 = (1 << ALIGN_SIZE_LOG2); -const FL_INDEX_MAX: u32 = sizeof() == 8 ? 32 : 30; -const SL_INDEX_COUNT: u32 = (1 << SL_INDEX_COUNT_LOG2); -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; - -assert(sizeof() * 8 >= SL_INDEX_COUNT); -assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT); - -/** - * Block header structure. - * - * There are several implementation subtleties involved: - * - The prev_phys_block field is only valid if the previous block is free. - * - The prev_phys_block field is actually stored at the end of the - * previous block. It appears at the beginning of this structure only to - * simplify the implementation. - * - The next_free / prev_free fields are only valid if the block is free. - */ +/** Block header structure. */ @explicit -class block_header_t { - - /* Points to the previous physical block. */ - prev_phys_block: block_header_t; - /* The size of this block, excluding the block header. */ - size: size_t; - /* Next free block. */ - next_free: block_header_t; - /* Previous free block. */ - prev_free: block_header_t; +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. */ + 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. */ + tagged_size: usize; + /** Next free block. Only valid if the block is free. */ + next_free: BlockHeader; + /** Previous free block. Only valid if the block is free. */ + prev_free: BlockHeader; } -const sizeof_block_header_t: usize = 3 * sizeof() * sizeof(); + +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: -// - bit 0: whether block is busy or free -// - bit 1: whether previous block is busy or free -const block_header_free_bit: size_t = 1 << 0; -const block_header_prev_free_bit: size_t = 1 << 1; +// 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: size_t = sizeof(); +const block_header_overhead: usize = sizeof(); // User data starts directly after the size field in a used block. -const block_start_offset: size_t = sizeof() + sizeof(); +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: size_t = sizeof_block_header_t - sizeof(); -const block_size_max: size_t = 1 << FL_INDEX_MAX; +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_t extends block_header_t { // 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. /* First level free list bitmap. */ fl_bitmap: u32; /** Second level free list bitmaps. */ @@ -93,115 +74,113 @@ class control_t extends block_header_t { // Empty lists point at this block to i return store(changetype(this) + offset + fl_index * sizeof(), sl_map); } /** Head of free lists. */ - blocks(fl_index: u32, sl_index: u32): block_header_t { - 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()); + 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()); } - blocks_set(fl_index: u32, sl_index: u32, block: block_header_t): 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); + 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 sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof(); -// block_header_t member functions. - -function block_size(block: block_header_t): size_t { - return block.size & ~(block_header_free_bit | block_header_prev_free_bit); +function block_size(block: BlockHeader): usize { + return block.tagged_size & ~(block_header_free_bit | block_header_prev_free_bit); } -function block_set_size(block: block_header_t, size: size_t): void { - var oldsize = block.size; - block.size = size | (oldsize & (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: block_header_t): bool { +function block_is_last(block: BlockHeader): bool { return block_size(block) == 0; } -function block_is_free(block: block_header_t): bool { - return (block.size & block_header_free_bit) == block_header_free_bit; +function block_is_free(block: BlockHeader): bool { + return (block.tagged_size & block_header_free_bit) == block_header_free_bit; } -function block_set_free(block: block_header_t): void { - block.size |= block_header_free_bit; +function block_set_free(block: BlockHeader): void { + block.tagged_size |= block_header_free_bit; } -function block_set_used(block: block_header_t): void { - block.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: block_header_t): bool { - return (block.size & block_header_prev_free_bit) == block_header_prev_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: block_header_t): void { - block.size |= 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: block_header_t): void { - block.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): block_header_t { - return changetype(ptr - block_start_offset); +function block_from_ptr(ptr: usize): BlockHeader { + return changetype(ptr - block_start_offset); } -function block_to_ptr(block: block_header_t): usize { +function block_to_ptr(block: BlockHeader): usize { return changetype(block) + block_start_offset; } /* Return location of next block after block of given size. */ -function offset_to_block(ptr: usize, size: size_t): block_header_t { - return changetype(ptr + size); +function offset_to_block(ptr: usize, size: usize): BlockHeader { + return changetype(ptr + size); } /* Return location of previous block. */ -function block_prev(block: block_header_t): block_header_t { +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: block_header_t): block_header_t { +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)); + 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: block_header_t): block_header_t { +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: block_header_t): void { +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: block_header_t): void { +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: size_t, align: size_t): size_t { - assert(0 == (align & (align - 1)), "must align to a power of two"); +function align_up(x: usize, align: usize): usize { + assert(!(align & (align - 1)), "must align to a power of two"); return (x + (align - 1)) & ~(align - 1); } -function align_down(x: size_t, align: size_t): size_t { - assert(0 == (align & (align - 1)), "must align to a power of two"); +function align_down(x: usize, align: usize): usize { + assert(!(align & (align - 1)), "must align to a power of two"); return x - (x & (align - 1)); } -function align_ptr(ptr: usize, align: size_t): usize { +function align_ptr(ptr: usize, align: usize): usize { var aligned = (ptr + (align - 1)) & ~(align - 1); - assert(0 == (align & (align - 1)), "must align to a power of two"); + assert(!(align & (align - 1)), "must align to a power of two"); return aligned; } @@ -209,14 +188,11 @@ function align_ptr(ptr: usize, align: size_t): usize { * Adjust an allocation size to be aligned to word size, and no smaller * than internal minimum. */ -function adjust_request_size(size: size_t, align: size_t): size_t { - var adjust: size_t = 0; - if (size) { +function adjust_request_size(size: usize, align: usize): usize { + var adjust: usize = 0; + if (size && size < block_size_max) { var aligned = align_up(size, align); - // aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap - if (aligned < block_size_max) { - adjust = max(aligned, block_size_min); - } + adjust = max(aligned, block_size_min); } return adjust; } @@ -226,14 +202,14 @@ function adjust_request_size(size: size_t, align: size_t): size_t { var fl_out: i32, sl_out: i32; -function mapping_insert(size: size_t): void { +function mapping_insert(size: usize): void { var fl: i32, sl: i32; if (size < SMALL_BLOCK_SIZE) { // Store small blocks in first list. fl = 0; sl = size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT); } else { - fl = fls(size); + fl = fls(size); sl = (size >> (fl - SL_INDEX_COUNT_LOG2)) ^ (1 << SL_INDEX_COUNT_LOG2); fl -= (FL_INDEX_SHIFT - 1); } @@ -241,35 +217,38 @@ function mapping_insert(size: size_t): void { sl_out = sl; } -function mapping_search(size: size_t): void { +function mapping_search(size: usize): void { if (size >= SMALL_BLOCK_SIZE) { - var round: size_t = (1 << (fls(size) - SL_INDEX_COUNT_LOG2)) - 1; + var round: usize = (1 << (fls(size) - SL_INDEX_COUNT_LOG2)) - 1; size += round; } mapping_insert(size); } -function search_suitable_block(control: control_t, fl: i32, sl: i32): block_header_t { +function search_suitable_block(control: Control, fl: i32, sl: i32): BlockHeader { + // First, search for a block in the list associated with the given + // fl/sl index. var sl_map = control.sl_bitmap(fl) & (~0 << sl); if (!sl_map) { // No block exists. Search in the next largest first-level list. var fl_map = control.fl_bitmap & (~0 << (fl + 1)); if (!fl_map) { // No free blocks available, memory has been exhausted. - return changetype(0); + return changetype(0); } fl = ffs(fl_map); fl_out = fl; sl_map = control.sl_bitmap(fl); } assert(sl_map, "internal error - second level bitmap is null"); - sl = ffs(sl_map); sl_out = sl; + // Return the first block in the free list. return control.blocks(fl, sl); } -function remove_free_block(control: control_t, block: block_header_t, fl: i32, sl: i32): void { +/* Remove a free block from the free list.*/ +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"); @@ -288,47 +267,49 @@ function remove_free_block(control: control_t, block: block_header_t, fl: i32, s } } -function insert_free_block(control: control_t, block: block_header_t, fl: i32, sl: i32): void { +/* Insert a free block into the free block list. */ +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"); + // Insert the new block at the head of the list, and mark the first- + // 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)) } - -function block_remove(control: control_t, block: block_header_t): void { +/* 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); } -function block_insert(control: control_t, block: block_header_t): void { +/* 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: block_header_t, size: size_t): bool { +function block_can_split(block: BlockHeader, size: usize): bool { return block_size(block) >= sizeof_block_header_t + size; } -function block_split(block: block_header_t, size: size_t): block_header_t { +/* 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); @@ -336,15 +317,17 @@ function block_split(block: block_header_t, size: size_t): block_header_t { return remaining; } -function block_absorb(prev: block_header_t, block: block_header_t): block_header_t { +/* 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"); - prev.size += block_size(block) + block_header_overhead; + // 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_t, block: block_header_t): block_header_t { +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"); @@ -356,8 +339,8 @@ function block_merge_prev(control: control_t, block: block_header_t): block_head } /* Merge a just-freed block with an adjacent free block. */ -function block_merge_next(control: control_t, block: block_header_t): block_header_t { - var next: block_header_t = block_next(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"); @@ -368,7 +351,7 @@ function block_merge_next(control: control_t, block: block_header_t): block_head } /* Trim any trailing block space off the end of a block, return to pool. */ -function block_trim_free(control: control_t, block: block_header_t, size: size_t): void { +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); @@ -379,10 +362,10 @@ function block_trim_free(control: control_t, block: block_header_t, size: size_t } /* Trim any trailing block space off the end of a used block, return to pool. */ -function block_trim_used(control: control_t, block: block_header_t, size: size_t): void { +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 + // 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); @@ -390,7 +373,7 @@ function block_trim_used(control: control_t, block: block_header_t, size: size_t } } -function block_trim_free_leading(control: control_t, block: block_header_t, size: size_t): block_header_t { +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); @@ -401,9 +384,9 @@ function block_trim_free_leading(control: control_t, block: block_header_t, size return remaining_block; } -function block_locate_free(control: control_t, size: size_t): block_header_t { +function block_locate_free(control: Control, size: usize): BlockHeader { var index: u64 = 0; - var block: block_header_t = changetype(0); + var block: BlockHeader = changetype(0); if (size) { mapping_search(size); if (fl_out < FL_INDEX_MAX) { @@ -417,7 +400,7 @@ function block_locate_free(control: control_t, size: size_t): block_header_t { return block; } -function block_prepare_used(control: control_t, block: block_header_t, size: size_t): usize { +function block_prepare_used(control: Control, block: BlockHeader, size: usize): usize { var p: usize = 0; if (block) { assert(size, "size must be non-zero"); @@ -429,7 +412,7 @@ function block_prepare_used(control: control_t, block: block_header_t, size: siz } /* Clear structure and point all empty lists at the null block. */ -function control_construct(control: control_t): void { +function control_construct(control: Control): void { control.next_free = control; control.prev_free = control; control.fl_bitmap = 0; @@ -441,34 +424,39 @@ function control_construct(control: control_t): void { } } -type tlsf_t = usize; -type pool_t = usize; +var TLSF: usize = 0; -var TLSF: tlsf_t = 0; - -function create(mem: usize): tlsf_t { +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() * 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 Error("Memory must be aligned"); - control_construct(changetype(mem)); + throw new RangeError("Memory must be aligned"); + control_construct(changetype(mem)); return mem; } -function create_with_pool(mem: usize, bytes: size_t): tlsf_t { +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: tlsf_t, mem: usize, bytes: size_t): pool_t { - var block: block_header_t; - var next: block_header_t; +function add_pool(tlsf: usize, mem: usize, bytes: usize): usize { + var block: BlockHeader; + var next: BlockHeader; - const pool_overhead: size_t = 2 * block_header_overhead; + // 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 Error("Memory must be aligned"); + throw new RangeError("Memory must be aligned"); if (pool_bytes < block_size_min || pool_bytes > block_size_max) - throw new Error("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 // so that the prev_phys_block field falls outside of the pool - @@ -477,7 +465,7 @@ function add_pool(tlsf: tlsf_t, mem: usize, bytes: size_t): pool_t { block_set_size(block, pool_bytes); block_set_free(block); block_set_prev_used(block); - block_insert(changetype(tlsf), block); + block_insert(changetype(tlsf), block); // Split the block to create a zero-size sentinel block. next = block_link_next(block); @@ -488,21 +476,92 @@ function add_pool(tlsf: tlsf_t, mem: usize, bytes: size_t): pool_t { return mem; } -export function allocate_memory(size: size_t): usize { +// Tests + +function test_ffs_fls(): i32 { + var rv = 0; + rv += (ffs(0) == -1) ? 0 : 0x1; + rv += (fls(0) == -1) ? 0 : 0x2; + rv += (ffs(1) == 0) ? 0 : 0x4; + rv += (fls(1) == 0) ? 0 : 0x8; + rv += (ffs(0x80000000) == 31) ? 0 : 0x10; + rv += (ffs(0x80008000) == 15) ? 0 : 0x20; + rv += (fls(0x80000008) == 31) ? 0 : 0x40; + rv += (fls(0x7FFFFFFF) == 30) ? 0 : 0x80; + rv += (fls(0x80000000) == 31) ? 0 : 0x100; + rv += (fls(0x100000000) == 32) ? 0 : 0x200; + rv += (fls(0xffffffffffffffff) == 63) ? 0 : 0x400; + return rv; +} + +export function check(): i32 { if (!TLSF) TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE); - var control = changetype(TLSF); + var control = changetype(TLSF); + var status = 0; + for (var i = 0; i < FL_INDEX_COUNT; ++i) { + for (var j = 0; j < SL_INDEX_COUNT; ++j) { + var fl_map = control.fl_bitmap & (1 << i); + var sl_list = control.sl_bitmap(i); + 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 (!sl_map) { + 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; + while (block != control) { + if (!assert(block_is_free(block), "block should be free")) + --status; + if (!assert(!block_is_prev_free(block), "blocks should have coalesced")) + --status; + if (!assert(!block_is_free(block_next(block)), "blocks should have coalesced")) + --status; + if (!assert(block_is_prev_free(block_next(block)), "block should be free")) + --status; + if (!assert(block_size(block) >= block_size_min, "block not minimum size")) + --status; + mapping_insert(block_size(block)); + if (!assert(fl_out == i && sl_out == j, "block size indexed in wrong list")) + --status; + block = block.next_free; + } + } + } + } + return status; +} + +// Exported interface + +function request_memory(size: usize): void { + // round size up to a full page + if (size & 0xffff) + size = (size | 0xffff) + 1; + // at least double memory for efficiency + var prev_pages = grow_memory(max(current_memory(), size >> 16)); + if (prev_pages < 0) // out of host memory + unreachable(); + var next_pages = current_memory(); + add_pool(TLSF, prev_pages << 16, (next_pages - prev_pages) << 16); +} + +export function allocate_memory(size: usize): usize { + if (!TLSF) { + TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE); + } + var control = changetype(TLSF); var adjust = adjust_request_size(size, ALIGN_SIZE); var block = block_locate_free(control, adjust); - if (!block && size > 0) { - if (size & 0xffff) - size = (size | 0xffff) + 1; - var oldsize = grow_memory(size >>> 16); - if (oldsize >= 0) { - add_pool(TLSF, oldsize << 16, size); - } else { - throw new Error("Out of memory"); - } + if (!block && adjust > 0) { + request_memory(adjust); block = block_locate_free(control, adjust); } return block_prepare_used(control, block, adjust); @@ -510,7 +569,7 @@ export function allocate_memory(size: size_t): usize { export function free_memory(ptr: usize): void { if (TLSF && ptr) { - var control = changetype(TLSF); + var control = changetype(TLSF); var block = block_from_ptr(ptr); assert(!block_is_free(block), "block already marked as free"); block_mark_as_free(block); @@ -519,22 +578,3 @@ export function free_memory(ptr: usize): void { block_insert(control, block); } } - -function test_ffs_fls(): i32 { - // Verify ffs/fls work properly. - var rv = 0; - rv += (ffs(0) == -1) ? 0 : 0x1; - rv += (fls(0) == -1) ? 0 : 0x2; - rv += (ffs(1) == 0) ? 0 : 0x4; - rv += (fls(1) == 0) ? 0 : 0x8; - rv += (ffs(0x80000000) == 31) ? 0 : 0x10; - rv += (ffs(0x80008000) == 15) ? 0 : 0x20; - rv += (fls(0x80000008) == 31) ? 0 : 0x40; - rv += (fls(0x7FFFFFFF) == 30) ? 0 : 0x80; - rv += (fls(0x80000000) == 31) ? 0 : 0x100; - rv += (fls(0x100000000) == 32) ? 0 : 0x200; - rv += (fls(0xffffffffffffffff) == 63) ? 0 : 0x400; - return rv; -} - -assert(!test_ffs_fls()); diff --git a/examples/tlsf/package.json b/examples/tlsf/package.json index d92ab5e1..cea56016 100644 --- a/examples/tlsf/package.json +++ b/examples/tlsf/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "npm run build:untouched && npm run build:optimized", "build:untouched": "asc assembly/tlsf.ts -t tlsf.untouched.wast -b tlsf.untouched.wasm --validate", - "build:optimized": "asc -O assembly/tlsf.ts -b tlsf.optimized.wasm -t tlsf.optimized.wast --validate --noAssert", + "build:optimized": "asc -O assembly/tlsf.ts -b tlsf.optimized.wasm -t tlsf.optimized.wast --validate --noAssert --runPasses inlining", "test": "node tests" } } diff --git a/examples/tlsf/tests/index.js b/examples/tlsf/tests/index.js index 9506d772..3f304dae 100644 --- a/examples/tlsf/tests/index.js +++ b/examples/tlsf/tests/index.js @@ -1,49 +1,63 @@ var fs = require("fs"); -var tlsf = new WebAssembly.Instance(WebAssembly.Module(fs.readFileSync(__dirname + "/../tlsf.optimized.wasm")), { - env: { - log_i: function(i) { i == -1 ? console.log("---") : console.log("log_i -> " + i); } - } -}).exports; -console.log(Object.keys(tlsf)); +function test(file) { + console.log("Testing '" + file + "' ..."); -try { - var memSize = 0; - for (var j = 0; j < 500; ++j) { - var ptr; - var ptrs = []; - for (var i = 0; i < 256; ++i) { - var size = i * 64; - ptr = tlsf.allocate_memory(size); - console.log("allocate_memory(" + size + ") = " + ptr); - if (!(i % 4)) { + var tlsf = new WebAssembly.Instance(WebAssembly.Module(fs.readFileSync(__dirname + "/../" + file)), { + env: { + log_i: function(i) { i == -1 ? console.log("---") : console.log("log_i -> " + i); } + } + }).exports; + + try { + var memSize = 0; + var ptr = 0; + for (var j = 0; j < 10000; ++j) { + if (!j || !((j + 1) % 1000)) + console.log("run #" + (j + 1)); + ptr; + var ptrs = []; + // allocate some blocks of unusual sizes + for (var i = 0; i < 2048; ++i) { + var size = i * 61; + ptr = tlsf.allocate_memory(size); + // immediately free every 4th + if (!(i % 4)) { + tlsf.free_memory(ptr); + } else { + ptrs.push(ptr); + // randomly free random blocks (if not the first run that determines max memory) + if (j && Math.random() < 0.25) { + ptr = ptrs.splice((Math.random() * ptrs.length)|0, 1)[0]; + tlsf.free_memory(ptr); + } + } + } + tlsf.check(); + // clean up by randomly freeing all blocks + while (ptrs.length) { + ptr = ptrs.splice((Math.random() * ptrs.length)|0, 1)[0]; tlsf.free_memory(ptr); - console.log("free_memory(" + ptr + ")"); - } else - ptrs.push(ptr); + } + if (memSize && memSize != tlsf.memory.buffer.byteLength) + throw new Error("memory should not grow multiple times: " + memSize + " != " + tlsf.memory.buffer.byteLength); + memSize = tlsf.memory.buffer.byteLength; + tlsf.check(); } - while (ptrs.length) { - ptr = Math.random() < 0.5 ? ptrs.pop() : ptrs.shift(); - console.log("free_memory(" + ptr + ")"); - tlsf.free_memory(ptr); - } - if (memSize && memSize != tlsf.memory.length) - throw new Error("memory should not grow multiple times"); - memSize = tlsf.memory.length; + } finally { + mem(tlsf.memory, 0, 4096); + console.log("memSize=" + memSize); } - var ptr = tlsf.allocate_memory(64); - console.log("allocate_memory(" + 64 + ") = " + ptr); -} catch (e) { - console.log(e.stack); - mem(tlsf.memory); + console.log(); } -function mem(memory, offset) { +function mem(memory, offset, count) { if (!offset) offset = 0; + if (!count) count = 1024; var mem = new Uint8Array(memory.buffer, offset); var stackTop = new Uint32Array(memory.buffer, 4, 1)[0]; var hex = []; - for (var i = 0; i < 1024; ++i) { + for (var i = 0; i < count; ++i) { var o = (offset + i).toString(16); while (o.length < 3) o = "0" + o; if ((i & 15) === 0) { @@ -55,3 +69,6 @@ function mem(memory, offset) { } console.log(hex.join(" ") + " ..."); } + +test("tlsf.untouched.wasm"); +test("tlsf.optimized.wasm");