mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-25 07:02:13 +00:00
Move TLSF to stdlib, see #15
This commit is contained in:
parent
9e9284955d
commit
c6486c461d
@ -21,12 +21,6 @@ A few early examples to get an idea:
|
||||
* **[PSON decoder](./examples/pson)**<br />
|
||||
A simple decoder for the PSON binary format.
|
||||
|
||||
* **[TLSF memory allocator](./examples/tlsf)**<br />
|
||||
An implementation of the TLSF memory allocator.
|
||||
|
||||
* **[μgc garbage collector](./examples/ugc)**<br />
|
||||
A port of the μgc garbage collector library.
|
||||
|
||||
Or browse the [compiler tests](./tests/compiler) for a more in-depth overview of what's supported already. One of them is a [showcase](./tests/compiler/showcase.ts).
|
||||
|
||||
Installation
|
||||
|
2
bin/asc
2
bin/asc
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
const asc = module.exports = require("./asc.js");
|
||||
if (process.argv[1] === __filename)
|
||||
if (/\basc$/.test(process.argv[1]))
|
||||
process.exitCode = asc.main(process.argv.slice(2));
|
||||
|
@ -1,20 +0,0 @@
|
||||
 TLSF
|
||||
=================
|
||||
|
||||
An implementation of the [Two Level Segregate Fit](http://www.gii.upv.es/tlsf/main/docs) memory allocator in AssemblyScript.
|
||||
|
||||
Instructions
|
||||
------------
|
||||
|
||||
To build [assembly/tlsf.ts](./assembly/tlsf.ts) to an untouched and an optimized `.wasm` including their respective `.wast` representations, run:
|
||||
|
||||
```
|
||||
$> npm run build
|
||||
```
|
||||
|
||||
Afterwards, to run the included [test](./tests/index.js):
|
||||
|
||||
```
|
||||
$> npm install
|
||||
$> npm test
|
||||
```
|
@ -1,491 +0,0 @@
|
||||
////////////// TLSF (Two-Level Segregate Fit) Memory Allocator ////////////////
|
||||
|
||||
// ╒══════════════ Block size interpretation (32-bit) ═════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┴─┴─╫─┴─┴─┤
|
||||
// │ | FL │ SB = SL + AL │ ◄─ usize
|
||||
// └───────────────────────────────────────────────┴─────────╨─────┘
|
||||
// FL: first level, SL: second level, AL: alignment, SB: small block
|
||||
|
||||
const AL_BITS: u32 = sizeof<usize>() == sizeof<u32>() ? 2 : 3;
|
||||
const AL_SIZE: usize = 1 << <usize>AL_BITS;
|
||||
const AL_MASK: usize = AL_SIZE - 1;
|
||||
|
||||
const SL_BITS: u32 = 5;
|
||||
const SL_SIZE: usize = 1 << <usize>SL_BITS;
|
||||
|
||||
const SB_BITS: usize = <usize>(SL_BITS + AL_BITS);
|
||||
const SB_SIZE: usize = 1 << <usize>SB_BITS;
|
||||
const SB_MASK: usize = SB_SIZE - 1;
|
||||
|
||||
const FL_BITS: u32 = (sizeof<usize>() == sizeof<u32>()
|
||||
? 30 // ^= up to 1GB per block
|
||||
: 32 // ^= up to 4GB per block
|
||||
) - SB_BITS;
|
||||
|
||||
// ╒════════════════ Block structure layout (32-bit) ══════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┤
|
||||
// │ size │L│F│ ◄─┐
|
||||
// ╞═══════════════════════════════════════════════════════════╧═╧═╡ │ ┐
|
||||
// │ if free: ◄ prev │ ◄─┤ usize
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ if free: next ► │ ◄─┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ ... unused free space >= 0 ... │ │ = 0
|
||||
// ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │
|
||||
// │ if free: jump ▲ │ ◄─┘
|
||||
// └───────────────────────────────────────────────────────────────┘ MIN SIZE ┘
|
||||
// F: FREE, L: LEFT_FREE
|
||||
|
||||
/** Tag indicating that this block is free. */
|
||||
const FREE: usize = 1 << 0;
|
||||
/** Tag indicating that this block's left block is free. */
|
||||
const LEFT_FREE: usize = 1 << 1;
|
||||
/** Mask to obtain all tags. */
|
||||
const TAGS: usize = FREE | LEFT_FREE;
|
||||
|
||||
assert(AL_BITS >= 2); // alignment must be large enough to store all tags
|
||||
|
||||
/** Block structure. */
|
||||
@unmanaged
|
||||
class Block {
|
||||
|
||||
/** Info field holding this block's size and tags. */
|
||||
info: usize;
|
||||
|
||||
/** End offset of the {@link Block#info} field. User data starts here. */
|
||||
static readonly INFO: usize = sizeof<usize>();
|
||||
|
||||
/** Previous free block, if any. Only valid if free. */
|
||||
prev: Block | null;
|
||||
/** Next free block, if any. Only valid if free. */
|
||||
next: Block | null;
|
||||
|
||||
/** Minimum size of a block, excluding {@link Block#info}. */
|
||||
static readonly MIN_SIZE: usize = 3 * sizeof<usize>(); // prev + next + jump
|
||||
|
||||
/** Maximum size of a used block, excluding {@link Block#info}. */
|
||||
static readonly MAX_SIZE: usize = 1 << (FL_BITS + SB_BITS);
|
||||
|
||||
/** Gets this block's left (free) block in memory. */
|
||||
get left(): Block {
|
||||
assert(this.info & LEFT_FREE); // must be free to contain a jump
|
||||
return assert(
|
||||
load<Block>(changetype<usize>(this) - sizeof<usize>())
|
||||
); // can't be null
|
||||
}
|
||||
|
||||
/** Gets this block's right block in memory. */
|
||||
get right(): Block {
|
||||
assert(this.info & ~TAGS); // can't skip beyond the tail block
|
||||
return assert(
|
||||
changetype<Block>(
|
||||
changetype<usize>(this) + Block.INFO + (this.info & ~TAGS)
|
||||
)
|
||||
); // can't be null
|
||||
}
|
||||
}
|
||||
|
||||
// ╒════════════════ Root structure layout (32-bit) ═══════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤ ┐
|
||||
// │ 0 | flMap S│ ◄────┐
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │
|
||||
// │ slMap[0] S │ ◄─┐ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ │ │
|
||||
// │ slMap[1] │ ◄─┤ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ u32 │
|
||||
// │ ... │ ◄─┤ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ │ │
|
||||
// │ slMap[22] P │ ◄─┘ │
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ usize
|
||||
// │ head[0] │ ◄────┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ ... │ ◄────┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ head[736] │ ◄────┤
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │
|
||||
// │ tailRef │ ◄────┘
|
||||
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
|
||||
// S: Small blocks map, P: Possibly padded if 64-bit
|
||||
|
||||
assert((1 << SL_BITS) <= 32); // second level must fit into 32 bits
|
||||
|
||||
/** Root structure. */
|
||||
@unmanaged
|
||||
class Root {
|
||||
|
||||
/** First level bitmap. */
|
||||
flMap: usize = 0;
|
||||
|
||||
/** Start offset of second level maps. */
|
||||
private static readonly SL_START: usize = sizeof<usize>();
|
||||
|
||||
// Using *one* SL map per *FL bit*
|
||||
|
||||
/** Gets the second level map for the specified first level. */
|
||||
getSLMap(fl: usize): u32 {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
return load<u32>(changetype<usize>(this) + fl * 4, Root.SL_START);
|
||||
}
|
||||
|
||||
/** Sets the second level map for the specified first level. */
|
||||
setSLMap(fl: usize, value: u32): void {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
store<u32>(changetype<usize>(this) + fl * 4, value, Root.SL_START);
|
||||
}
|
||||
|
||||
/** End offset of second level maps. */
|
||||
private static readonly SL_END: usize = Root.SL_START + FL_BITS * 4;
|
||||
|
||||
// Using *number bits per SL* heads per *FL bit*
|
||||
|
||||
/** Start offset of FL/SL heads. */
|
||||
private static readonly HL_START: usize = (Root.SL_END + AL_MASK) & ~AL_MASK;
|
||||
|
||||
/** Gets the head of the specified first and second level index. */
|
||||
getHead(fl: usize, sl: u32): Block | null {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
assert(sl < SL_SIZE); // sl out of range
|
||||
return changetype<Block>(load<usize>(
|
||||
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>()
|
||||
, Root.HL_START));
|
||||
}
|
||||
|
||||
/** Sets the head of the specified first and second level index. */
|
||||
setHead(fl: usize, sl: u32, value: Block | null): void {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
assert(sl < SL_SIZE); // sl out of range
|
||||
store<usize>(
|
||||
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>()
|
||||
, changetype<usize>(value)
|
||||
, Root.HL_START);
|
||||
}
|
||||
|
||||
/** End offset of FL/SL heads. */
|
||||
private static readonly HL_END: usize = (
|
||||
Root.HL_START + FL_BITS * SL_SIZE * sizeof<usize>()
|
||||
);
|
||||
|
||||
get tailRef(): usize { return load<usize>(0, Root.HL_END); }
|
||||
set tailRef(value: usize) { store<usize>(0, value, Root.HL_END); }
|
||||
|
||||
/** Total size of the {@link Root} structure. */
|
||||
static readonly SIZE: usize = Root.HL_END + sizeof<usize>();
|
||||
|
||||
/** Inserts a previously used block back into the free list. */
|
||||
insert(block: Block): void {
|
||||
// check as much as possible here to prevent invalid free blocks
|
||||
assert(block); // cannot be null
|
||||
var blockInfo = block.info;
|
||||
assert(blockInfo & FREE); // must be free
|
||||
var size: usize;
|
||||
assert(
|
||||
(size = block.info & ~TAGS) >= Block.MIN_SIZE && size < Block.MAX_SIZE
|
||||
); // must be valid, not necessary to compute yet if noAssert=true
|
||||
|
||||
var right: Block = assert(block.right); // can't be null
|
||||
var rightInfo = right.info;
|
||||
|
||||
// merge with right block if also free
|
||||
if (rightInfo & FREE) {
|
||||
this.remove(right);
|
||||
block.info = (blockInfo += Block.INFO + (rightInfo & ~TAGS));
|
||||
right = block.right;
|
||||
rightInfo = right.info;
|
||||
// jump is set below
|
||||
}
|
||||
|
||||
// merge with left block if also free
|
||||
if (blockInfo & LEFT_FREE) {
|
||||
var left: Block = assert(block.left); // can't be null
|
||||
var leftInfo = left.info;
|
||||
assert(leftInfo & FREE); // must be free according to tags
|
||||
this.remove(left);
|
||||
left.info = (leftInfo += Block.INFO + (blockInfo & ~TAGS));
|
||||
block = left;
|
||||
blockInfo = leftInfo;
|
||||
// jump is set below
|
||||
}
|
||||
|
||||
right.info = rightInfo | LEFT_FREE;
|
||||
this.setJump(block, right);
|
||||
// right is no longer used now, hence rightInfo is not synced
|
||||
|
||||
size = blockInfo & ~TAGS;
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
|
||||
|
||||
// mapping_insert
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
fl = fls<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
|
||||
// perform insertion
|
||||
var head = this.getHead(fl, sl);
|
||||
block.prev = null;
|
||||
block.next = head;
|
||||
if (head) head.prev = block;
|
||||
this.setHead(fl, sl, block);
|
||||
|
||||
// update first and second level maps
|
||||
this.flMap |= (1 << fl);
|
||||
this.setSLMap(fl, this.getSLMap(fl) | (1 << sl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a free block from FL/SL maps. Does not alter left/jump because it
|
||||
* is likely that splitting is performed afterwards, invalidating any changes
|
||||
* again.
|
||||
*/
|
||||
private remove(block: Block): void {
|
||||
var blockInfo = block.info;
|
||||
assert(blockInfo & FREE); // must be free
|
||||
var size = blockInfo & ~TAGS;
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
|
||||
|
||||
// mapping_insert
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
fl = fls<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
|
||||
// link previous and next free block
|
||||
var prev = block.prev;
|
||||
var next = block.next;
|
||||
if (prev)
|
||||
prev.next = next;
|
||||
if (next)
|
||||
next.prev = prev;
|
||||
|
||||
// update head if we are removing it
|
||||
if (block == this.getHead(fl, sl)) {
|
||||
this.setHead(fl, sl, next);
|
||||
|
||||
// clear second level map if head is empty now
|
||||
if (!next) {
|
||||
var slMap = this.getSLMap(fl);
|
||||
this.setSLMap(fl, slMap &= ~(1 << sl));
|
||||
|
||||
// clear first level map if second level is empty now
|
||||
if (!slMap)
|
||||
this.flMap &= ~(1 << fl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Searches for a free block of at least the specified size. */
|
||||
search(size: usize): Block | null {
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE);
|
||||
|
||||
// mapping_search
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
// (*) size += (1 << (fls<usize>(size) - SL_BITS)) - 1;
|
||||
fl = fls<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
// (*) instead of rounding up, use next second level list for better fit
|
||||
if (sl < SL_SIZE - 1) ++sl;
|
||||
else ++fl, sl = 0;
|
||||
}
|
||||
|
||||
// search second level
|
||||
var slMap = this.getSLMap(fl) & (~0 << sl);
|
||||
var head: Block | null;
|
||||
if (!slMap) {
|
||||
// search next larger first level
|
||||
var flMap = this.flMap & (~0 << (fl + 1));
|
||||
if (!flMap) {
|
||||
head = null;
|
||||
} else {
|
||||
fl = ffs<usize>(flMap);
|
||||
slMap = assert(this.getSLMap(fl)); // can't be zero if fl points here
|
||||
head = this.getHead(fl, ffs<u32>(slMap));
|
||||
}
|
||||
} else
|
||||
head = this.getHead(fl, ffs<u32>(slMap));
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
/** Links a free left with its right block in memory. */
|
||||
private setJump(left: Block, right: Block): void {
|
||||
assert(left.info & FREE); // must be free
|
||||
assert(left.right == right); // right block must match
|
||||
assert(right.info & LEFT_FREE); // right block must be tagged as LEFT_FREE
|
||||
store<Block>(
|
||||
changetype<usize>(right) - sizeof<usize>()
|
||||
, left); // last word in left block's (free) data region
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the specified free block, removing it from internal maps and
|
||||
* splitting it if possible, and returns its data pointer.
|
||||
*/
|
||||
use(block: Block, size: usize): usize {
|
||||
var blockInfo = block.info;
|
||||
assert(blockInfo & FREE); // must be free so we can use it
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
|
||||
assert(!(size & AL_MASK)); // size must be aligned so the new block is
|
||||
|
||||
this.remove(block);
|
||||
|
||||
// split if the block can hold another MIN_SIZE block
|
||||
var remaining = (blockInfo & ~TAGS) - size;
|
||||
if (remaining >= Block.INFO + Block.MIN_SIZE) {
|
||||
block.info = size | (blockInfo & LEFT_FREE); // also discards FREE
|
||||
|
||||
var spare = changetype<Block>(
|
||||
changetype<usize>(block) + Block.INFO + size
|
||||
);
|
||||
spare.info = (remaining - Block.INFO) | FREE; // not LEFT_FREE
|
||||
this.insert(spare); // also sets jump
|
||||
|
||||
// otherwise tag block as no longer FREE and right as no longer LEFT_FREE
|
||||
} else {
|
||||
block.info = blockInfo & ~FREE;
|
||||
var right: Block = assert(block.right); // can't be null (tail)
|
||||
right.info &= ~LEFT_FREE;
|
||||
}
|
||||
|
||||
return changetype<usize>(block) + Block.INFO;
|
||||
}
|
||||
|
||||
/** Adds more memory to the pool. */
|
||||
addMemory(start: usize, end: usize): bool {
|
||||
assert(start <= end);
|
||||
assert(!(start & AL_MASK)); // must be aligned
|
||||
assert(!(end & AL_MASK)); // must be aligned
|
||||
|
||||
var tailRef = this.tailRef;
|
||||
var tailInfo: usize = 0;
|
||||
if (tailRef) {
|
||||
assert(start >= tailRef + sizeof<usize>()); // starts after tail
|
||||
|
||||
// merge with current tail if adjacent
|
||||
if (start - Block.INFO == tailRef) {
|
||||
start -= Block.INFO;
|
||||
tailInfo = changetype<Block>(tailRef).info;
|
||||
}
|
||||
|
||||
} else
|
||||
assert(start >= changetype<usize>(this) + Root.SIZE); // starts after root
|
||||
|
||||
// check if size is large enough for a free block and the tail block
|
||||
var size = end - start;
|
||||
if (size < Block.INFO + Block.MIN_SIZE + Block.INFO)
|
||||
return false;
|
||||
|
||||
// left size is total minus its own and the zero-length tail's header
|
||||
var leftSize = size - 2 * Block.INFO;
|
||||
var left = changetype<Block>(start);
|
||||
left.info = leftSize | FREE | (tailInfo & LEFT_FREE);
|
||||
left.prev = null;
|
||||
left.next = null;
|
||||
|
||||
// tail is a zero-length used block
|
||||
var tail = changetype<Block>(start + size - Block.INFO);
|
||||
tail.info = 0 | LEFT_FREE;
|
||||
this.tailRef = changetype<usize>(tail);
|
||||
|
||||
this.insert(left); // also merges with free left before tail / sets jump
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines the first (LSB to MSB) set bit's index of a word. */
|
||||
function ffs<T>(word: T): T {
|
||||
assert(word != 0); // word cannot be 0
|
||||
return ctz<T>(word); // differs from ffs only for 0
|
||||
}
|
||||
|
||||
/** Determines the last (LSB to MSB) set bit's index of a word. */
|
||||
function fls<T>(word: T): T {
|
||||
assert(word != 0); // word cannot be 0
|
||||
const inv: T = (sizeof<T>() << 3) - 1;
|
||||
return inv - clz<T>(word);
|
||||
}
|
||||
|
||||
/** Reference to the initialized {@link Root} structure, once initialized. */
|
||||
var ROOT: Root = changetype<Root>(0);
|
||||
|
||||
// External interface
|
||||
|
||||
/** Allocates a chunk of memory. */
|
||||
export function allocate_memory(size: usize): usize {
|
||||
|
||||
// initialize if necessary
|
||||
var root = ROOT;
|
||||
if (!root) {
|
||||
var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
|
||||
ROOT = root = changetype<Root>(rootOffset);
|
||||
root.tailRef = 0;
|
||||
root.flMap = 0;
|
||||
for (var fl: usize = 0; fl < FL_BITS; ++fl) {
|
||||
root.setSLMap(fl, 0);
|
||||
for (var sl: u32 = 0; sl < SL_SIZE; ++sl)
|
||||
root.setHead(fl, sl, null);
|
||||
}
|
||||
root.addMemory(rootOffset + Root.SIZE, current_memory() << 16);
|
||||
}
|
||||
|
||||
// search for a suitable block
|
||||
var data: usize = 0;
|
||||
if (size && size < Block.MAX_SIZE) {
|
||||
size = max<usize>((size + AL_MASK) & ~AL_MASK, Block.MIN_SIZE);
|
||||
|
||||
var block = root.search(size);
|
||||
if (!block) {
|
||||
|
||||
// request more memory
|
||||
var pagesBefore = current_memory();
|
||||
var pagesWanted = max(pagesBefore, ((size + 0xffff) & ~0xffff) >>> 16);
|
||||
if (grow_memory(pagesWanted) < 0)
|
||||
unreachable(); // out of memory
|
||||
var pagesAfter = current_memory();
|
||||
root.addMemory(<usize>pagesBefore << 16, <usize>pagesAfter << 16);
|
||||
block = assert(root.search(size)); // must be found now
|
||||
}
|
||||
|
||||
assert((block.info & ~TAGS) >= size);
|
||||
data = root.use(block, size);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Frees the chunk of memory at the specified address. */
|
||||
export function free_memory(data: usize): void {
|
||||
if (data) {
|
||||
var root = ROOT;
|
||||
if (root) {
|
||||
var block = changetype<Block>(data - Block.INFO);
|
||||
var blockInfo = block.info;
|
||||
assert(!(blockInfo & FREE)); // must be used
|
||||
block.info = blockInfo | FREE;
|
||||
root.insert(changetype<Block>(data - Block.INFO));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For stand-alone usage, e.g., in tests
|
||||
// export { move_memory, set_memory, compare_memory };
|
@ -1,5 +0,0 @@
|
||||
const fs = require("fs");
|
||||
module.exports = new WebAssembly.Instance(
|
||||
new WebAssembly.Module(fs.readFileSync(__dirname + "/tlsf.optimized.wasm")),
|
||||
{ env: { abort: function() { throw Error("abort called"); } } }
|
||||
).exports;
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@assemblyscript/tlsf",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"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 --sourceMap --measure",
|
||||
"build:optimized": "asc assembly/tlsf.ts -t tlsf.optimized.wast -b tlsf.optimized.wasm --validate --sourceMap --measure --noDebug --noAssert --optimize",
|
||||
"test": "node tests",
|
||||
"test:forever": "node tests/forever"
|
||||
}
|
||||
}
|
@ -1,12 +1,488 @@
|
||||
////////////// TLSF (Two-Level Segregate Fit) Memory Allocator ////////////////
|
||||
|
||||
// Re-export for now, so there's just one source file being worked on
|
||||
// ╒══════════════ Block size interpretation (32-bit) ═════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┴─┴─╫─┴─┴─┤
|
||||
// │ | FL │ SB = SL + AL │ ◄─ usize
|
||||
// └───────────────────────────────────────────────┴─────────╨─────┘
|
||||
// FL: first level, SL: second level, AL: alignment, SB: small block
|
||||
|
||||
export {
|
||||
allocate_memory,
|
||||
free_memory
|
||||
} from "../../../examples/tlsf/assembly/tlsf";
|
||||
const AL_BITS: u32 = sizeof<usize>() == sizeof<u32>() ? 2 : 3;
|
||||
const AL_SIZE: usize = 1 << <usize>AL_BITS;
|
||||
const AL_MASK: usize = AL_SIZE - 1;
|
||||
|
||||
export function reset_memory(): void {
|
||||
throw new Error("not supported");
|
||||
const SL_BITS: u32 = 5;
|
||||
const SL_SIZE: usize = 1 << <usize>SL_BITS;
|
||||
|
||||
const SB_BITS: usize = <usize>(SL_BITS + AL_BITS);
|
||||
const SB_SIZE: usize = 1 << <usize>SB_BITS;
|
||||
const SB_MASK: usize = SB_SIZE - 1;
|
||||
|
||||
const FL_BITS: u32 = (sizeof<usize>() == sizeof<u32>()
|
||||
? 30 // ^= up to 1GB per block
|
||||
: 32 // ^= up to 4GB per block
|
||||
) - SB_BITS;
|
||||
|
||||
// ╒════════════════ Block structure layout (32-bit) ══════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┤
|
||||
// │ size │L│F│ ◄─┐
|
||||
// ╞═══════════════════════════════════════════════════════════╧═╧═╡ │ ┐
|
||||
// │ if free: ◄ prev │ ◄─┤ usize
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ if free: next ► │ ◄─┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ ... unused free space >= 0 ... │ │ = 0
|
||||
// ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │
|
||||
// │ if free: jump ▲ │ ◄─┘
|
||||
// └───────────────────────────────────────────────────────────────┘ MIN SIZE ┘
|
||||
// F: FREE, L: LEFT_FREE
|
||||
|
||||
/** Tag indicating that this block is free. */
|
||||
const FREE: usize = 1 << 0;
|
||||
/** Tag indicating that this block's left block is free. */
|
||||
const LEFT_FREE: usize = 1 << 1;
|
||||
/** Mask to obtain all tags. */
|
||||
const TAGS: usize = FREE | LEFT_FREE;
|
||||
|
||||
assert(AL_BITS >= 2); // alignment must be large enough to store all tags
|
||||
|
||||
/** Block structure. */
|
||||
@unmanaged
|
||||
class Block {
|
||||
|
||||
/** Info field holding this block's size and tags. */
|
||||
info: usize;
|
||||
|
||||
/** End offset of the {@link Block#info} field. User data starts here. */
|
||||
static readonly INFO: usize = sizeof<usize>();
|
||||
|
||||
/** Previous free block, if any. Only valid if free. */
|
||||
prev: Block | null;
|
||||
/** Next free block, if any. Only valid if free. */
|
||||
next: Block | null;
|
||||
|
||||
/** Minimum size of a block, excluding {@link Block#info}. */
|
||||
static readonly MIN_SIZE: usize = 3 * sizeof<usize>(); // prev + next + jump
|
||||
|
||||
/** Maximum size of a used block, excluding {@link Block#info}. */
|
||||
static readonly MAX_SIZE: usize = 1 << (FL_BITS + SB_BITS);
|
||||
|
||||
/** Gets this block's left (free) block in memory. */
|
||||
get left(): Block {
|
||||
assert(this.info & LEFT_FREE); // must be free to contain a jump
|
||||
return assert(
|
||||
load<Block>(changetype<usize>(this) - sizeof<usize>())
|
||||
); // can't be null
|
||||
}
|
||||
|
||||
/** Gets this block's right block in memory. */
|
||||
get right(): Block {
|
||||
assert(this.info & ~TAGS); // can't skip beyond the tail block
|
||||
return assert(
|
||||
changetype<Block>(
|
||||
changetype<usize>(this) + Block.INFO + (this.info & ~TAGS)
|
||||
)
|
||||
); // can't be null
|
||||
}
|
||||
}
|
||||
|
||||
// ╒════════════════ Root structure layout (32-bit) ═══════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤ ┐
|
||||
// │ 0 | flMap S│ ◄────┐
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │
|
||||
// │ slMap[0] S │ ◄─┐ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ │ │
|
||||
// │ slMap[1] │ ◄─┤ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ u32 │
|
||||
// │ ... │ ◄─┤ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ │ │
|
||||
// │ slMap[22] P │ ◄─┘ │
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ usize
|
||||
// │ head[0] │ ◄────┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ ... │ ◄────┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ head[736] │ ◄────┤
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │
|
||||
// │ tailRef │ ◄────┘
|
||||
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
|
||||
// S: Small blocks map, P: Possibly padded if 64-bit
|
||||
|
||||
assert((1 << SL_BITS) <= 32); // second level must fit into 32 bits
|
||||
|
||||
/** Root structure. */
|
||||
@unmanaged
|
||||
class Root {
|
||||
|
||||
/** First level bitmap. */
|
||||
flMap: usize = 0;
|
||||
|
||||
/** Start offset of second level maps. */
|
||||
private static readonly SL_START: usize = sizeof<usize>();
|
||||
|
||||
// Using *one* SL map per *FL bit*
|
||||
|
||||
/** Gets the second level map for the specified first level. */
|
||||
getSLMap(fl: usize): u32 {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
return load<u32>(changetype<usize>(this) + fl * 4, Root.SL_START);
|
||||
}
|
||||
|
||||
/** Sets the second level map for the specified first level. */
|
||||
setSLMap(fl: usize, value: u32): void {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
store<u32>(changetype<usize>(this) + fl * 4, value, Root.SL_START);
|
||||
}
|
||||
|
||||
/** End offset of second level maps. */
|
||||
private static readonly SL_END: usize = Root.SL_START + FL_BITS * 4;
|
||||
|
||||
// Using *number bits per SL* heads per *FL bit*
|
||||
|
||||
/** Start offset of FL/SL heads. */
|
||||
private static readonly HL_START: usize = (Root.SL_END + AL_MASK) & ~AL_MASK;
|
||||
|
||||
/** Gets the head of the specified first and second level index. */
|
||||
getHead(fl: usize, sl: u32): Block | null {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
assert(sl < SL_SIZE); // sl out of range
|
||||
return changetype<Block>(load<usize>(
|
||||
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>()
|
||||
, Root.HL_START));
|
||||
}
|
||||
|
||||
/** Sets the head of the specified first and second level index. */
|
||||
setHead(fl: usize, sl: u32, value: Block | null): void {
|
||||
assert(fl < FL_BITS); // fl out of range
|
||||
assert(sl < SL_SIZE); // sl out of range
|
||||
store<usize>(
|
||||
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>()
|
||||
, changetype<usize>(value)
|
||||
, Root.HL_START);
|
||||
}
|
||||
|
||||
/** End offset of FL/SL heads. */
|
||||
private static readonly HL_END: usize = (
|
||||
Root.HL_START + FL_BITS * SL_SIZE * sizeof<usize>()
|
||||
);
|
||||
|
||||
get tailRef(): usize { return load<usize>(0, Root.HL_END); }
|
||||
set tailRef(value: usize) { store<usize>(0, value, Root.HL_END); }
|
||||
|
||||
/** Total size of the {@link Root} structure. */
|
||||
static readonly SIZE: usize = Root.HL_END + sizeof<usize>();
|
||||
|
||||
/** Inserts a previously used block back into the free list. */
|
||||
insert(block: Block): void {
|
||||
// check as much as possible here to prevent invalid free blocks
|
||||
assert(block); // cannot be null
|
||||
var blockInfo = block.info;
|
||||
assert(blockInfo & FREE); // must be free
|
||||
var size: usize;
|
||||
assert(
|
||||
(size = block.info & ~TAGS) >= Block.MIN_SIZE && size < Block.MAX_SIZE
|
||||
); // must be valid, not necessary to compute yet if noAssert=true
|
||||
|
||||
var right: Block = assert(block.right); // can't be null
|
||||
var rightInfo = right.info;
|
||||
|
||||
// merge with right block if also free
|
||||
if (rightInfo & FREE) {
|
||||
this.remove(right);
|
||||
block.info = (blockInfo += Block.INFO + (rightInfo & ~TAGS));
|
||||
right = block.right;
|
||||
rightInfo = right.info;
|
||||
// jump is set below
|
||||
}
|
||||
|
||||
// merge with left block if also free
|
||||
if (blockInfo & LEFT_FREE) {
|
||||
var left: Block = assert(block.left); // can't be null
|
||||
var leftInfo = left.info;
|
||||
assert(leftInfo & FREE); // must be free according to tags
|
||||
this.remove(left);
|
||||
left.info = (leftInfo += Block.INFO + (blockInfo & ~TAGS));
|
||||
block = left;
|
||||
blockInfo = leftInfo;
|
||||
// jump is set below
|
||||
}
|
||||
|
||||
right.info = rightInfo | LEFT_FREE;
|
||||
this.setJump(block, right);
|
||||
// right is no longer used now, hence rightInfo is not synced
|
||||
|
||||
size = blockInfo & ~TAGS;
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
|
||||
|
||||
// mapping_insert
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
fl = fls<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
|
||||
// perform insertion
|
||||
var head = this.getHead(fl, sl);
|
||||
block.prev = null;
|
||||
block.next = head;
|
||||
if (head) head.prev = block;
|
||||
this.setHead(fl, sl, block);
|
||||
|
||||
// update first and second level maps
|
||||
this.flMap |= (1 << fl);
|
||||
this.setSLMap(fl, this.getSLMap(fl) | (1 << sl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a free block from FL/SL maps. Does not alter left/jump because it
|
||||
* is likely that splitting is performed afterwards, invalidating any changes
|
||||
* again.
|
||||
*/
|
||||
private remove(block: Block): void {
|
||||
var blockInfo = block.info;
|
||||
assert(blockInfo & FREE); // must be free
|
||||
var size = blockInfo & ~TAGS;
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
|
||||
|
||||
// mapping_insert
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
fl = fls<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
|
||||
// link previous and next free block
|
||||
var prev = block.prev;
|
||||
var next = block.next;
|
||||
if (prev)
|
||||
prev.next = next;
|
||||
if (next)
|
||||
next.prev = prev;
|
||||
|
||||
// update head if we are removing it
|
||||
if (block == this.getHead(fl, sl)) {
|
||||
this.setHead(fl, sl, next);
|
||||
|
||||
// clear second level map if head is empty now
|
||||
if (!next) {
|
||||
var slMap = this.getSLMap(fl);
|
||||
this.setSLMap(fl, slMap &= ~(1 << sl));
|
||||
|
||||
// clear first level map if second level is empty now
|
||||
if (!slMap)
|
||||
this.flMap &= ~(1 << fl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Searches for a free block of at least the specified size. */
|
||||
search(size: usize): Block | null {
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE);
|
||||
|
||||
// mapping_search
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
// (*) size += (1 << (fls<usize>(size) - SL_BITS)) - 1;
|
||||
fl = fls<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
// (*) instead of rounding up, use next second level list for better fit
|
||||
if (sl < SL_SIZE - 1) ++sl;
|
||||
else ++fl, sl = 0;
|
||||
}
|
||||
|
||||
// search second level
|
||||
var slMap = this.getSLMap(fl) & (~0 << sl);
|
||||
var head: Block | null;
|
||||
if (!slMap) {
|
||||
// search next larger first level
|
||||
var flMap = this.flMap & (~0 << (fl + 1));
|
||||
if (!flMap) {
|
||||
head = null;
|
||||
} else {
|
||||
fl = ffs<usize>(flMap);
|
||||
slMap = assert(this.getSLMap(fl)); // can't be zero if fl points here
|
||||
head = this.getHead(fl, ffs<u32>(slMap));
|
||||
}
|
||||
} else
|
||||
head = this.getHead(fl, ffs<u32>(slMap));
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
/** Links a free left with its right block in memory. */
|
||||
private setJump(left: Block, right: Block): void {
|
||||
assert(left.info & FREE); // must be free
|
||||
assert(left.right == right); // right block must match
|
||||
assert(right.info & LEFT_FREE); // right block must be tagged as LEFT_FREE
|
||||
store<Block>(
|
||||
changetype<usize>(right) - sizeof<usize>()
|
||||
, left); // last word in left block's (free) data region
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the specified free block, removing it from internal maps and
|
||||
* splitting it if possible, and returns its data pointer.
|
||||
*/
|
||||
use(block: Block, size: usize): usize {
|
||||
var blockInfo = block.info;
|
||||
assert(blockInfo & FREE); // must be free so we can use it
|
||||
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
|
||||
assert(!(size & AL_MASK)); // size must be aligned so the new block is
|
||||
|
||||
this.remove(block);
|
||||
|
||||
// split if the block can hold another MIN_SIZE block
|
||||
var remaining = (blockInfo & ~TAGS) - size;
|
||||
if (remaining >= Block.INFO + Block.MIN_SIZE) {
|
||||
block.info = size | (blockInfo & LEFT_FREE); // also discards FREE
|
||||
|
||||
var spare = changetype<Block>(
|
||||
changetype<usize>(block) + Block.INFO + size
|
||||
);
|
||||
spare.info = (remaining - Block.INFO) | FREE; // not LEFT_FREE
|
||||
this.insert(spare); // also sets jump
|
||||
|
||||
// otherwise tag block as no longer FREE and right as no longer LEFT_FREE
|
||||
} else {
|
||||
block.info = blockInfo & ~FREE;
|
||||
var right: Block = assert(block.right); // can't be null (tail)
|
||||
right.info &= ~LEFT_FREE;
|
||||
}
|
||||
|
||||
return changetype<usize>(block) + Block.INFO;
|
||||
}
|
||||
|
||||
/** Adds more memory to the pool. */
|
||||
addMemory(start: usize, end: usize): bool {
|
||||
assert(start <= end);
|
||||
assert(!(start & AL_MASK)); // must be aligned
|
||||
assert(!(end & AL_MASK)); // must be aligned
|
||||
|
||||
var tailRef = this.tailRef;
|
||||
var tailInfo: usize = 0;
|
||||
if (tailRef) {
|
||||
assert(start >= tailRef + sizeof<usize>()); // starts after tail
|
||||
|
||||
// merge with current tail if adjacent
|
||||
if (start - Block.INFO == tailRef) {
|
||||
start -= Block.INFO;
|
||||
tailInfo = changetype<Block>(tailRef).info;
|
||||
}
|
||||
|
||||
} else
|
||||
assert(start >= changetype<usize>(this) + Root.SIZE); // starts after root
|
||||
|
||||
// check if size is large enough for a free block and the tail block
|
||||
var size = end - start;
|
||||
if (size < Block.INFO + Block.MIN_SIZE + Block.INFO)
|
||||
return false;
|
||||
|
||||
// left size is total minus its own and the zero-length tail's header
|
||||
var leftSize = size - 2 * Block.INFO;
|
||||
var left = changetype<Block>(start);
|
||||
left.info = leftSize | FREE | (tailInfo & LEFT_FREE);
|
||||
left.prev = null;
|
||||
left.next = null;
|
||||
|
||||
// tail is a zero-length used block
|
||||
var tail = changetype<Block>(start + size - Block.INFO);
|
||||
tail.info = 0 | LEFT_FREE;
|
||||
this.tailRef = changetype<usize>(tail);
|
||||
|
||||
this.insert(left); // also merges with free left before tail / sets jump
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines the first (LSB to MSB) set bit's index of a word. */
|
||||
function ffs<T>(word: T): T {
|
||||
assert(word != 0); // word cannot be 0
|
||||
return ctz<T>(word); // differs from ffs only for 0
|
||||
}
|
||||
|
||||
/** Determines the last (LSB to MSB) set bit's index of a word. */
|
||||
function fls<T>(word: T): T {
|
||||
assert(word != 0); // word cannot be 0
|
||||
const inv: T = (sizeof<T>() << 3) - 1;
|
||||
return inv - clz<T>(word);
|
||||
}
|
||||
|
||||
/** Reference to the initialized {@link Root} structure, once initialized. */
|
||||
var ROOT: Root = changetype<Root>(0);
|
||||
|
||||
// External interface
|
||||
|
||||
/** Allocates a chunk of memory. */
|
||||
export function allocate_memory(size: usize): usize {
|
||||
|
||||
// initialize if necessary
|
||||
var root = ROOT;
|
||||
if (!root) {
|
||||
var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
|
||||
ROOT = root = changetype<Root>(rootOffset);
|
||||
root.tailRef = 0;
|
||||
root.flMap = 0;
|
||||
for (var fl: usize = 0; fl < FL_BITS; ++fl) {
|
||||
root.setSLMap(fl, 0);
|
||||
for (var sl: u32 = 0; sl < SL_SIZE; ++sl)
|
||||
root.setHead(fl, sl, null);
|
||||
}
|
||||
root.addMemory(rootOffset + Root.SIZE, current_memory() << 16);
|
||||
}
|
||||
|
||||
// search for a suitable block
|
||||
var data: usize = 0;
|
||||
if (size && size < Block.MAX_SIZE) {
|
||||
size = max<usize>((size + AL_MASK) & ~AL_MASK, Block.MIN_SIZE);
|
||||
|
||||
var block = root.search(size);
|
||||
if (!block) {
|
||||
|
||||
// request more memory
|
||||
var pagesBefore = current_memory();
|
||||
var pagesWanted = max(pagesBefore, ((size + 0xffff) & ~0xffff) >>> 16);
|
||||
if (grow_memory(pagesWanted) < 0)
|
||||
unreachable(); // out of memory
|
||||
var pagesAfter = current_memory();
|
||||
root.addMemory(<usize>pagesBefore << 16, <usize>pagesAfter << 16);
|
||||
block = assert(root.search(size)); // must be found now
|
||||
}
|
||||
|
||||
assert((block.info & ~TAGS) >= size);
|
||||
data = root.use(block, size);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Frees the chunk of memory at the specified address. */
|
||||
export function free_memory(data: usize): void {
|
||||
if (data) {
|
||||
var root = ROOT;
|
||||
if (root) {
|
||||
var block = changetype<Block>(data - Block.INFO);
|
||||
var blockInfo = block.info;
|
||||
assert(!(blockInfo & FREE)); // must be used
|
||||
block.info = blockInfo | FREE;
|
||||
root.insert(changetype<Block>(data - Block.INFO));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
tests/tlsf/assembly/index.ts
Normal file
2
tests/tlsf/assembly/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import "allocator/tlsf";
|
||||
export { allocate_memory, free_memory };
|
@ -5,7 +5,7 @@ var child_process = require("child_process");
|
||||
var count = 0;
|
||||
while (true) {
|
||||
console.log("[ #" + ++count + " ]\n");
|
||||
var res = child_process.spawnSync("node", [ "tests" ], { stdio: "inherit" });
|
||||
var res = child_process.spawnSync("node", [ __dirname ], { stdio: "inherit" });
|
||||
if (res.status !== 0)
|
||||
throw Error("exited with " + res.status);
|
||||
if (res.error)
|
@ -4,7 +4,7 @@ const runner = require("./runner");
|
||||
function test(file) {
|
||||
console.log("Testing '" + file + "' ...\n");
|
||||
|
||||
const exports = new WebAssembly.Instance(WebAssembly.Module(fs.readFileSync(__dirname + "/../" + file)), {
|
||||
const exports = new WebAssembly.Instance(WebAssembly.Module(fs.readFileSync(__dirname + "/" + file)), {
|
||||
env: {
|
||||
abort: function(msg, file, line, column) {
|
||||
throw Error("Assertion failed: " + (msg ? "'" + getString(msg) + "' " : "") + "at " + getString(file) + ":" + line + ":" + column);
|
10
tests/tlsf/package.json
Normal file
10
tests/tlsf/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build:untouched && npm run build:optimized",
|
||||
"build:untouched": "asc assembly/index.ts -t tlsf.untouched.wast -b tlsf.untouched.wasm --validate --sourceMap --measure",
|
||||
"build:optimized": "asc assembly/index.ts -t tlsf.optimized.wast -b tlsf.optimized.wasm --validate --sourceMap --measure --noDebug --noAssert --optimize",
|
||||
"test": "node ./index",
|
||||
"test:forever": "node ./forever"
|
||||
}
|
||||
}
|
1437
tests/tlsf/tlsf.optimized.wast
Normal file
1437
tests/tlsf/tlsf.optimized.wast
Normal file
File diff suppressed because it is too large
Load Diff
2781
tests/tlsf/tlsf.untouched.wast
Normal file
2781
tests/tlsf/tlsf.untouched.wast
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user