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)
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) {

View File

@ -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"
}
}

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
// 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<usize>() == 8 ? 3 : 2;
const ALIGN_SIZE: u32 = 1 << ALIGN_SIZE_LOG2;
const FL_INDEX_MAX: u32 = sizeof<usize>() == 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<T>(word: i32): i32 {
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;
}
const SL_INDEX_COUNT_LOG2: u32 = 5;
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.
*/
/** 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<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
// 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<size_t>();
const block_header_overhead: usize = sizeof<usize>();
// 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
// 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<usize>();
const block_size_max: size_t = 1 << FL_INDEX_MAX;
const block_size_min: usize = sizeof_block_header_t - sizeof<usize>();
const block_size_max: usize = <usize>1 << FL_INDEX_MAX;
/* The TLSF control structure. */
@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<u32>(changetype<usize>(this) + offset + fl_index * sizeof<u32>(), sl_map);
}
/** Head of free lists. */
blocks(fl_index: u32, sl_index: u32): block_header_t {
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>());
blocks(fl_index: u32, sl_index: u32): BlockHeader {
const offset: usize = sizeof_block_header_t + sizeof<u32>() + FL_INDEX_COUNT * sizeof<u32>();
return load<BlockHeader>(changetype<usize>(this) + offset + (fl_index * SL_INDEX_COUNT + sl_index) * sizeof<usize>());
}
blocks_set(fl_index: u32, sl_index: u32, block: block_header_t): void {
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);
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>();
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>();
// 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<block_header_t>(ptr - block_start_offset);
function block_from_ptr(ptr: usize): BlockHeader {
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 location of next block after block of given size. */
function offset_to_block(ptr: usize, size: size_t): block_header_t {
return changetype<block_header_t>(ptr + size);
function offset_to_block(ptr: usize, size: usize): BlockHeader {
return changetype<BlockHeader>(ptr + <usize>size);
}
/* Return location of previous block. */
function block_prev(block: 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,15 +188,12 @@ 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);
}
}
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 = <i32>size / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
} else {
fl = fls<size_t>(size);
fl = fls<usize>(size);
sl = (<i32>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_t>(size) - SL_INDEX_COUNT_LOG2)) - 1;
var round: usize = (<usize>1 << (fls<usize>(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) & (<u32>~0 << sl);
if (!sl_map) {
// No block exists. Search in the next largest first-level list.
var fl_map = control.fl_bitmap & (<u32>~0 << (fl + 1));
if (!fl_map) {
// No free blocks available, memory has been exhausted.
return changetype<block_header_t>(0);
return changetype<BlockHeader>(0);
}
fl = ffs<u32>(fl_map);
fl_out = fl;
sl_map = control.sl_bitmap(fl);
}
assert(sl_map, "internal error - second level bitmap is null");
sl = ffs<u32>(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<block_header_t>(0);
var block: BlockHeader = changetype<BlockHeader>(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<u32>() * 8 >= SL_INDEX_COUNT);
// Ensure we've properly tuned our sizes.
assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT);
if ((mem % ALIGN_SIZE) != 0)
throw new Error("Memory must be aligned");
control_construct(changetype<control_t>(mem));
throw new RangeError("Memory must be aligned");
control_construct(changetype<Control>(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<control_t>(tlsf), block);
block_insert(changetype<Control>(tlsf), block);
// Split the block to create a zero-size sentinel block.
next = block_link_next(block);
@ -488,40 +476,9 @@ function add_pool(tlsf: tlsf_t, mem: usize, bytes: size_t): pool_t {
return mem;
}
export function allocate_memory(size: size_t): usize {
if (!TLSF)
TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
var control = changetype<control_t>(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(<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);
}
return block_prepare_used(control, block, adjust);
}
export function free_memory(ptr: usize): void {
if (TLSF && ptr) {
var control = changetype<control_t>(TLSF);
var block = block_from_ptr(ptr);
assert(!block_is_free(block), "block already marked as free");
block_mark_as_free(block);
block = block_merge_prev(control, block);
block = block_merge_next(control, block);
block_insert(control, block);
}
}
// Tests
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;
@ -537,4 +494,87 @@ function test_ffs_fls(): i32 {
return rv;
}
assert(!test_ffs_fls());
export function check(): i32 {
if (!TLSF)
TLSF = create_with_pool(HEAP_BASE, (current_memory() << 16) - HEAP_BASE);
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 block = block_locate_free(control, adjust);
if (!block && adjust > 0) {
request_memory(adjust);
block = block_locate_free(control, adjust);
}
return block_prepare_used(control, block, adjust);
}
export function free_memory(ptr: usize): void {
if (TLSF && ptr) {
var control = changetype<Control>(TLSF);
var block = block_from_ptr(ptr);
assert(!block_is_free(block), "block already marked as free");
block_mark_as_free(block);
block = block_merge_prev(control, block);
block = block_merge_next(control, block);
block_insert(control, block);
}
}

View File

@ -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"
}
}

View File

@ -1,49 +1,63 @@
var fs = require("fs");
var tlsf = new WebAssembly.Instance(WebAssembly.Module(fs.readFileSync(__dirname + "/../tlsf.optimized.wasm")), {
function test(file) {
console.log("Testing '" + file + "' ...");
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;
}).exports;
console.log(Object.keys(tlsf));
try {
try {
var memSize = 0;
for (var j = 0; j < 500; ++j) {
var ptr;
var ptr = 0;
for (var j = 0; j < 10000; ++j) {
if (!j || !((j + 1) % 1000))
console.log("run #" + (j + 1));
ptr;
var ptrs = [];
for (var i = 0; i < 256; ++i) {
var size = i * 64;
// allocate some blocks of unusual sizes
for (var i = 0; i < 2048; ++i) {
var size = i * 61;
ptr = tlsf.allocate_memory(size);
console.log("allocate_memory(" + size + ") = " + ptr);
// immediately free every 4th
if (!(i % 4)) {
tlsf.free_memory(ptr);
console.log("free_memory(" + ptr + ")");
} else
} else {
ptrs.push(ptr);
}
while (ptrs.length) {
ptr = Math.random() < 0.5 ? ptrs.pop() : ptrs.shift();
console.log("free_memory(" + 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);
}
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("allocate_memory(" + 64 + ") = " + ptr);
} catch (e) {
console.log(e.stack);
mem(tlsf.memory);
}
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);
}
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();
}
} finally {
mem(tlsf.memory, 0, 4096);
console.log("memSize=" + memSize);
}
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");