More TLSF

This commit is contained in:
dcodeIO 2018-01-16 17:52:48 +01:00
parent 867e037ff0
commit 7d5e56cef5
6 changed files with 324 additions and 221 deletions

View File

@ -167,6 +167,12 @@ else if (args.trapMode !== "allow") {
if (args.optimize) if (args.optimize)
module.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; var hasOutput = false;
if (args.outFile != null) { if (args.outFile != null) {

View File

@ -58,11 +58,15 @@
"trapMode": { "trapMode": {
"desc": [ "desc": [
"Sets the trap mode to use.", "Sets the trap mode to use.",
"allow Allow trapping operations. This is the default.", " allow Allow trapping operations. This is the default.",
"clamp Replace trapping operations with clamping semantics.", " clamp Replace trapping operations with clamping semantics.",
"js Replace trapping operations with JS semantics." " js Replace trapping operations with JS semantics."
], ],
"type": "string", "type": "string",
"default": "allow" "default": "allow"
},
"runPasses": {
"desc": "Specifies additional Binaryen passes to run.",
"type": "string"
} }
} }

View File

@ -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.

View File

@ -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 const SL_INDEX_COUNT_LOG2: u32 = 5;
// the search for a free block to a free list of guaranteed size
// adequate to fulfill the request, combined with efficient free list // Internal constants
// queries using bitmasks and architecture-specific bit-manipulation
// routines. const ALIGN_SIZE_LOG2: u32 = sizeof<usize>() == 8 ? 3 : 2;
// const ALIGN_SIZE: u32 = 1 << ALIGN_SIZE_LOG2;
// NOTE: TLSF spec relies on ffs/fls returning values 0..31 with -1 const FL_INDEX_MAX: u32 = sizeof<usize>() == 8 ? 32 : 30;
// indicating that no bits are set. In WebAssembly, ctz and clz return const SL_INDEX_COUNT: u32 = 1 << SL_INDEX_COUNT_LOG2;
// 32/64 if no bits are set. 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<T>(word: i32): i32 { function ffs<T>(word: i32): i32 {
return word ? <i32>ctz(word) : -1; return word ? <i32>ctz(word) : -1;
@ -20,67 +24,44 @@ function fls<T>(word: T): i32 {
return (<i32>sizeof<T>() << 3) - <i32>clz(word) - 1; return (<i32>sizeof<T>() << 3) - <i32>clz(word) - 1;
} }
const SL_INDEX_COUNT_LOG2: u32 = 5; /** Block header structure. */
const ALIGN_SIZE_LOG2: u32 = sizeof<size_t>() == 8 ? 3 : 2;
const ALIGN_SIZE: u32 = (1 << ALIGN_SIZE_LOG2);
const FL_INDEX_MAX: u32 = sizeof<size_t>() == 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<u32>() * 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.
*/
@explicit @explicit
class block_header_t { class BlockHeader {
/** Points to the previous physical block. Only valid if the previous block
/* Points to the previous physical block. */ * is free. Actually stored at the end of the previous block. */
prev_phys_block: block_header_t; prev_phys_block: BlockHeader;
/* The size of this block, excluding the block header. */ /** The size of this block, excluding the block header. The two least
size: size_t; * significant bits are used to store the block status. */
/* Next free block. */ tagged_size: usize;
next_free: block_header_t; /** Next free block. Only valid if the block is free. */
/* Previous free block. */ next_free: BlockHeader;
prev_free: block_header_t; /** Previous free block. Only valid if the block is free. */
prev_free: BlockHeader;
} }
const sizeof_block_header_t: usize = 3 * sizeof<usize>() * sizeof<size_t>();
const sizeof_block_header_t: usize = 4 * sizeof<usize>();
// Since block sizes are always at least a multiple of 4, the two least // 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: // significant bits of the size field are used to store the block status.
// - bit 0: whether block is busy or free const block_header_free_bit: usize = 1 << 0;
// - bit 1: whether previous block is busy or free const block_header_prev_free_bit: usize = 1 << 1;
const block_header_free_bit: size_t = 1 << 0;
const block_header_prev_free_bit: size_t = 1 << 1;
// The size of the block header exposed to used blocks is the size field. // 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. // The prev_phys_block field is stored *inside* the previous free block.
const block_header_overhead: size_t = sizeof<size_t>(); const block_header_overhead: usize = sizeof<usize>();
// User data starts directly after the size field in a used block. // User data starts directly after the size field in a used block.
const block_start_offset: size_t = sizeof<usize>() + sizeof<size_t>(); const block_start_offset: usize = sizeof<usize>() + sizeof<usize>();
// A free block must be large enough to store its header minus the size of // 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 // the prev_phys_block field, and no larger than the number of addressable
// bits for FL_INDEX. // bits for FL_INDEX.
const block_size_min: size_t = sizeof_block_header_t - sizeof<usize>(); const block_size_min: usize = sizeof_block_header_t - sizeof<usize>();
const block_size_max: size_t = 1 << FL_INDEX_MAX; const block_size_max: usize = <usize>1 << FL_INDEX_MAX;
/* The TLSF control structure. */ /* The TLSF control structure. */
@explicit @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. */ /* First level free list bitmap. */
fl_bitmap: u32; fl_bitmap: u32;
/** Second level free list bitmaps. */ /** 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<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>(), sl_map); return store<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>(), sl_map);
} }
/** Head of free lists. */ /** Head of free lists. */
blocks(fl_index: u32, sl_index: u32): block_header_t { blocks(fl_index: u32, sl_index: u32): BlockHeader {
const offset: usize = sizeof_block_header_t + sizeof<u32>() * FL_INDEX_COUNT * sizeof<u32>(); const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
return load<block_header_t>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>()); return load<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>());
} }
blocks_set(fl_index: u32, sl_index: u32, block: block_header_t): void { blocks_set(fl_index: u32, sl_index: u32, block: BlockHeader): void {
const offset: usize = sizeof_block_header_t + sizeof<u32>() * FL_INDEX_COUNT * sizeof<u32>(); const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
return store<block_header_t>(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);
} }
} }
const sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof<u32>() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof<usize>(); const sizeof_control_t: usize = sizeof_block_header_t + (1 + FL_INDEX_COUNT) * sizeof<u32>() + FL_INDEX_COUNT * SL_INDEX_COUNT * sizeof<usize>();
// block_header_t member functions. function block_size(block: BlockHeader): usize {
return block.tagged_size & ~(block_header_free_bit | block_header_prev_free_bit);
function block_size(block: block_header_t): size_t {
return block.size & ~(block_header_free_bit | block_header_prev_free_bit);
} }
function block_set_size(block: block_header_t, size: size_t): void { function block_set_size(block: BlockHeader, size: usize): void {
var oldsize = block.size; block.tagged_size = size | (block.tagged_size & (block_header_free_bit | block_header_prev_free_bit));
block.size = size | (oldsize & (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; return block_size(block) == 0;
} }
function block_is_free(block: block_header_t): bool { function block_is_free(block: BlockHeader): bool {
return (block.size & block_header_free_bit) == block_header_free_bit; return (block.tagged_size & block_header_free_bit) == block_header_free_bit;
} }
function block_set_free(block: block_header_t): void { function block_set_free(block: BlockHeader): void {
block.size |= block_header_free_bit; block.tagged_size |= block_header_free_bit;
} }
function block_set_used(block: block_header_t): void { function block_set_used(block: BlockHeader): void {
block.size &= ~block_header_free_bit; block.tagged_size &= ~block_header_free_bit;
} }
function block_is_prev_free(block: block_header_t): bool { function block_is_prev_free(block: BlockHeader): bool {
return (block.size & block_header_prev_free_bit) == block_header_prev_free_bit; return (block.tagged_size & block_header_prev_free_bit) == block_header_prev_free_bit;
} }
function block_set_prev_free(block: block_header_t): void { function block_set_prev_free(block: BlockHeader): void {
block.size |= block_header_prev_free_bit; block.tagged_size |= block_header_prev_free_bit;
} }
function block_set_prev_used(block: block_header_t): void { function block_set_prev_used(block: BlockHeader): void {
block.size &= ~block_header_prev_free_bit; block.tagged_size &= ~block_header_prev_free_bit;
} }
function block_from_ptr(ptr: usize): block_header_t { function block_from_ptr(ptr: usize): BlockHeader {
return changetype<block_header_t>(ptr - block_start_offset); return changetype<BlockHeader>(ptr - block_start_offset);
} }
function block_to_ptr(block: block_header_t): usize { function block_to_ptr(block: BlockHeader): usize {
return changetype<usize>(block) + block_start_offset; return changetype<usize>(block) + block_start_offset;
} }
/* Return location of next block after block of given size. */ /* Return location of next block after block of given size. */
function offset_to_block(ptr: usize, size: size_t): block_header_t { function offset_to_block(ptr: usize, size: usize): BlockHeader {
return changetype<block_header_t>(ptr + size); return changetype<BlockHeader>(ptr + <usize>size);
} }
/* Return location of previous block. */ /* 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"); assert(block_is_prev_free(block), "previous block must be free");
return block.prev_phys_block; return block.prev_phys_block;
} }
/* Return location of next existing 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); 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; return next;
} }
/* Link a new block with its physical neighbor, return the neighbor. */ /* 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); var next = block_next(block);
next.prev_phys_block = block; next.prev_phys_block = block;
return next; 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. // Link the block to the next block, first.
var next = block_link_next(block); var next = block_link_next(block);
block_set_prev_free(next); block_set_prev_free(next);
block_set_free(block); 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); var next = block_next(block);
block_set_prev_used(next); block_set_prev_used(next);
block_set_used(block); block_set_used(block);
} }
function align_up(x: size_t, align: size_t): size_t { function align_up(x: usize, align: usize): usize {
assert(0 == (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: size_t, align: size_t): size_t { function align_down(x: usize, align: usize): usize {
assert(0 == (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: size_t): usize { function align_ptr(ptr: usize, align: usize): usize {
var aligned = (ptr + (align - 1)) & ~(align - 1); 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; 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 * Adjust an allocation size to be aligned to word size, and no smaller
* than internal minimum. * than internal minimum.
*/ */
function adjust_request_size(size: size_t, align: size_t): size_t { function adjust_request_size(size: usize, align: usize): usize {
var adjust: size_t = 0; var adjust: usize = 0;
if (size) { if (size && size < block_size_max) {
var aligned = align_up(size, align); var aligned = align_up(size, align);
// aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap adjust = max(aligned, block_size_min);
if (aligned < block_size_max) {
adjust = max(aligned, block_size_min);
}
} }
return adjust; 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; 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; var fl: i32, sl: i32;
if (size < SMALL_BLOCK_SIZE) { if (size < SMALL_BLOCK_SIZE) {
// Store small blocks in first list. // Store small blocks in first list.
fl = 0; fl = 0;
sl = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT); sl = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
} else { } else {
fl = fls<size_t>(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);
} }
@ -241,35 +217,38 @@ function mapping_insert(size: size_t): void {
sl_out = sl; sl_out = sl;
} }
function mapping_search(size: size_t): void { function mapping_search(size: usize): void {
if (size >= SMALL_BLOCK_SIZE) { if (size >= SMALL_BLOCK_SIZE) {
var round: size_t = (1 << (fls<size_t>(size) - SL_INDEX_COUNT_LOG2)) - 1; var round: usize = (<usize>1 << (fls<usize>(size) - SL_INDEX_COUNT_LOG2)) - 1;
size += round; size += round;
} }
mapping_insert(size); 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) & (<u32>~0 << sl); var sl_map = control.sl_bitmap(fl) & (<u32>~0 << sl);
if (!sl_map) { if (!sl_map) {
// No block exists. Search in the next largest first-level list. // No block exists. Search in the next largest first-level list.
var fl_map = control.fl_bitmap & (<u32>~0 << (fl + 1)); var fl_map = control.fl_bitmap & (<u32>~0 << (fl + 1));
if (!fl_map) { if (!fl_map) {
// No free blocks available, memory has been exhausted. // No free blocks available, memory has been exhausted.
return changetype<block_header_t>(0); return changetype<BlockHeader>(0);
} }
fl = ffs<u32>(fl_map); fl = ffs<u32>(fl_map);
fl_out = fl; fl_out = fl;
sl_map = control.sl_bitmap(fl); sl_map = control.sl_bitmap(fl);
} }
assert(sl_map, "internal error - second level bitmap is null"); assert(sl_map, "internal error - second level bitmap is null");
sl = ffs<u32>(sl_map); sl = ffs<u32>(sl_map);
sl_out = sl; sl_out = sl;
// Return the first block in the free list.
return control.blocks(fl, sl); 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 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, "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); var current = control.blocks(fl, sl);
assert(current, "free list cannot have a null entry"); assert(current, "free list cannot have a null entry");
assert(block, "cannot insert a null entry into the free list"); assert(block, "cannot insert a null entry into the free list");
block.next_free = current; block.next_free = current;
block.prev_free = control; block.prev_free = control;
current.prev_free = block; current.prev_free = block;
assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not aligned properly"); 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.blocks_set(fl, sl, block);
control.fl_bitmap |= (1 << fl); control.fl_bitmap |= (1 << fl);
control.sl_bitmap_set(fl, control.sl_bitmap(fl) | (1 << sl)) control.sl_bitmap_set(fl, control.sl_bitmap(fl) | (1 << sl))
} }
/* Remove a given block from the free list. */
function block_remove(control: control_t, block: block_header_t): void { function block_remove(control: Control, block: BlockHeader): void {
mapping_insert(block_size(block)); mapping_insert(block_size(block));
remove_free_block(control, block, fl_out, sl_out); 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)); mapping_insert(block_size(block));
insert_free_block(control, block, fl_out, sl_out); 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; 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 remaining = offset_to_block(block_to_ptr(block), size - block_header_overhead);
var remain_size = block_size(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_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); assert(block_size(block) == remain_size + size + block_header_overhead);
block_set_size(remaining, remain_size); block_set_size(remaining, remain_size);
assert(block_size(remaining) >= block_size_min, "block split with invalid size"); assert(block_size(remaining) >= block_size_min, "block split with invalid size");
block_set_size(block, 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; 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"); 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); block_link_next(prev);
return prev; return prev;
} }
/* Merge a just-freed block with an adjacent previous free block. */ /* 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)) { if (block_is_prev_free(block)) {
var prev = block_prev(block); var prev = block_prev(block);
assert(prev, "prev physical block can't be null"); 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. */ /* Merge a just-freed block with an adjacent free block. */
function block_merge_next(control: control_t, block: block_header_t): block_header_t { function block_merge_next(control: Control, block: BlockHeader): BlockHeader {
var next: block_header_t = block_next(block); var next = block_next(block);
assert(next, "next physical block can't be null"); assert(next, "next physical block can't be null");
if (block_is_free(next)) { if (block_is_free(next)) {
assert(!block_is_last(block), "previous block can't be last"); 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. */ /* 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"); assert(block_is_free(block), "block must be free");
if (block_can_split(block, size)) { if (block_can_split(block, size)) {
var remaining_block = block_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. */ /* 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"); assert(!block_is_free(block), "block must be used");
if (block_can_split(block, size)) { 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); var remaining_block = block_split(block, size);
block_set_prev_used(remaining_block); block_set_prev_used(remaining_block);
remaining_block = block_merge_next(control, 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; var remaining_block = block;
if (block_can_split(block, size)) { if (block_can_split(block, size)) {
remaining_block = block_split(block, size - block_header_overhead); 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; 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 index: u64 = 0;
var block: block_header_t = changetype<block_header_t>(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) {
@ -417,7 +400,7 @@ function block_locate_free(control: control_t, size: size_t): block_header_t {
return block; 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; var p: usize = 0;
if (block) { if (block) {
assert(size, "size must be non-zero"); 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. */ /* 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.next_free = control;
control.prev_free = control; control.prev_free = control;
control.fl_bitmap = 0; control.fl_bitmap = 0;
@ -441,34 +424,39 @@ function control_construct(control: control_t): void {
} }
} }
type tlsf_t = usize; var TLSF: usize = 0;
type pool_t = usize;
var TLSF: tlsf_t = 0; function create(mem: usize): usize {
// Verify ffs/fls work properly
function create(mem: usize): tlsf_t { assert(!test_ffs_fls());
// SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type
assert(sizeof<u32>() * 8 >= SL_INDEX_COUNT);
// Ensure we've properly tuned our sizes.
assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
if ((mem % ALIGN_SIZE) != 0) if ((mem % ALIGN_SIZE) != 0)
throw new Error("Memory must be aligned"); throw new RangeError("Memory must be aligned");
control_construct(changetype<control_t>(mem)); control_construct(changetype<Control>(mem));
return 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); var tlsf = create(mem);
add_pool(tlsf, mem + sizeof_control_t, bytes - sizeof_control_t); add_pool(tlsf, mem + sizeof_control_t, bytes - sizeof_control_t);
return tlsf; return tlsf;
} }
function add_pool(tlsf: tlsf_t, mem: usize, bytes: size_t): pool_t { function add_pool(tlsf: usize, mem: usize, bytes: usize): usize {
var block: block_header_t; var block: BlockHeader;
var next: block_header_t; 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); var pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE);
if ((mem % ALIGN_SIZE) != 0) 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) 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 // 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 -
@ -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_size(block, pool_bytes);
block_set_free(block); block_set_free(block);
block_set_prev_used(block); block_set_prev_used(block);
block_insert(changetype<control_t>(tlsf), block); block_insert(changetype<Control>(tlsf), block);
// Split the block to create a zero-size sentinel block. // Split the block to create a zero-size sentinel block.
next = block_link_next(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; return mem;
} }
export function allocate_memory(size: size_t): usize { // Tests
function test_ffs_fls(): i32 {
var rv = 0;
rv += (ffs<u32>(0) == -1) ? 0 : 0x1;
rv += (fls<u32>(0) == -1) ? 0 : 0x2;
rv += (ffs<u32>(1) == 0) ? 0 : 0x4;
rv += (fls<u32>(1) == 0) ? 0 : 0x8;
rv += (ffs<u32>(0x80000000) == 31) ? 0 : 0x10;
rv += (ffs<u32>(0x80008000) == 15) ? 0 : 0x20;
rv += (fls<u32>(0x80000008) == 31) ? 0 : 0x40;
rv += (fls<u32>(0x7FFFFFFF) == 30) ? 0 : 0x80;
rv += (fls<u64>(0x80000000) == 31) ? 0 : 0x100;
rv += (fls<u64>(0x100000000) == 32) ? 0 : 0x200;
rv += (fls<u64>(0xffffffffffffffff) == 63) ? 0 : 0x400;
return rv;
}
export function check(): i32 {
if (!TLSF) if (!TLSF)
TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE); TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
var control = changetype<control_t>(TLSF); var control = changetype<Control>(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<u32>(current_memory(), <u32>size >> 16));
if (prev_pages < 0) // out of host memory
unreachable();
var next_pages = current_memory();
add_pool(TLSF, <usize>prev_pages << 16, <usize>(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<Control>(TLSF);
var adjust = adjust_request_size(size, ALIGN_SIZE); var adjust = adjust_request_size(size, ALIGN_SIZE);
var block = block_locate_free(control, adjust); var block = block_locate_free(control, adjust);
if (!block && size > 0) { if (!block && adjust > 0) {
if (size & 0xffff) request_memory(adjust);
size = (size | 0xffff) + 1;
var oldsize = grow_memory(<u32>size >>> 16);
if (oldsize >= 0) {
add_pool(TLSF, <usize>oldsize << 16, size);
} else {
throw new Error("Out of memory");
}
block = block_locate_free(control, adjust); block = block_locate_free(control, adjust);
} }
return block_prepare_used(control, block, 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 { export function free_memory(ptr: usize): void {
if (TLSF && ptr) { if (TLSF && ptr) {
var control = changetype<control_t>(TLSF); var control = changetype<Control>(TLSF);
var block = block_from_ptr(ptr); var block = block_from_ptr(ptr);
assert(!block_is_free(block), "block already marked as free"); assert(!block_is_free(block), "block already marked as free");
block_mark_as_free(block); block_mark_as_free(block);
@ -519,22 +578,3 @@ export function free_memory(ptr: usize): void {
block_insert(control, block); block_insert(control, block);
} }
} }
function test_ffs_fls(): i32 {
// Verify ffs/fls work properly.
var rv = 0;
rv += (ffs<u32>(0) == -1) ? 0 : 0x1;
rv += (fls<u32>(0) == -1) ? 0 : 0x2;
rv += (ffs<u32>(1) == 0) ? 0 : 0x4;
rv += (fls<u32>(1) == 0) ? 0 : 0x8;
rv += (ffs<u32>(0x80000000) == 31) ? 0 : 0x10;
rv += (ffs<u32>(0x80008000) == 15) ? 0 : 0x20;
rv += (fls<u32>(0x80000008) == 31) ? 0 : 0x40;
rv += (fls<u32>(0x7FFFFFFF) == 30) ? 0 : 0x80;
rv += (fls<u64>(0x80000000) == 31) ? 0 : 0x100;
rv += (fls<u64>(0x100000000) == 32) ? 0 : 0x200;
rv += (fls<u64>(0xffffffffffffffff) == 63) ? 0 : 0x400;
return rv;
}
assert(!test_ffs_fls());

View File

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"build": "npm run build:untouched && npm run build:optimized", "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: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" "test": "node tests"
} }
} }

View File

@ -1,49 +1,63 @@
var fs = require("fs"); 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 tlsf = new WebAssembly.Instance(WebAssembly.Module(fs.readFileSync(__dirname + "/../" + file)), {
var memSize = 0; env: {
for (var j = 0; j < 500; ++j) { log_i: function(i) { i == -1 ? console.log("---") : console.log("log_i -> " + i); }
var ptr; }
var ptrs = []; }).exports;
for (var i = 0; i < 256; ++i) {
var size = i * 64; try {
ptr = tlsf.allocate_memory(size); var memSize = 0;
console.log("allocate_memory(" + size + ") = " + ptr); var ptr = 0;
if (!(i % 4)) { 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); tlsf.free_memory(ptr);
console.log("free_memory(" + ptr + ")"); }
} else if (memSize && memSize != tlsf.memory.buffer.byteLength)
ptrs.push(ptr); throw new Error("memory should not grow multiple times: " + memSize + " != " + tlsf.memory.buffer.byteLength);
memSize = tlsf.memory.buffer.byteLength;
tlsf.check();
} }
while (ptrs.length) { } finally {
ptr = Math.random() < 0.5 ? ptrs.pop() : ptrs.shift(); mem(tlsf.memory, 0, 4096);
console.log("free_memory(" + ptr + ")"); console.log("memSize=" + memSize);
tlsf.free_memory(ptr);
}
if (memSize && memSize != tlsf.memory.length)
throw new Error("memory should not grow multiple times");
memSize = tlsf.memory.length;
} }
var ptr = tlsf.allocate_memory(64); console.log();
console.log("allocate_memory(" + 64 + ") = " + ptr);
} catch (e) {
console.log(e.stack);
mem(tlsf.memory);
} }
function mem(memory, offset) { function mem(memory, offset, count) {
if (!offset) offset = 0; if (!offset) offset = 0;
if (!count) count = 1024;
var mem = new Uint8Array(memory.buffer, offset); var mem = new Uint8Array(memory.buffer, offset);
var stackTop = new Uint32Array(memory.buffer, 4, 1)[0]; var stackTop = new Uint32Array(memory.buffer, 4, 1)[0];
var hex = []; var hex = [];
for (var i = 0; i < 1024; ++i) { for (var i = 0; i < count; ++i) {
var o = (offset + i).toString(16); var o = (offset + i).toString(16);
while (o.length < 3) o = "0" + o; while (o.length < 3) o = "0" + o;
if ((i & 15) === 0) { if ((i & 15) === 0) {
@ -55,3 +69,6 @@ function mem(memory, offset) {
} }
console.log(hex.join(" ") + " ..."); console.log(hex.join(" ") + " ...");
} }
test("tlsf.untouched.wasm");
test("tlsf.optimized.wasm");