From a7e815dcecaa624cfcabbacc821d4ca24ce1cd0d Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Tue, 6 Feb 2018 06:47:37 +0100 Subject: [PATCH] TLSF: Coalesce pages by extending the tail block, see #15 --- examples/tlsf/assembly/tlsf.ts | 40 ++++++++--- examples/tlsf/package.json | 3 +- examples/tlsf/tests/forever.js | 13 ++++ examples/tlsf/tests/index.js | 2 +- examples/tlsf/tests/runner.js | 49 ++++++------- src/types.ts | 121 ++++++++++++++++++++++++++++----- 6 files changed, 177 insertions(+), 51 deletions(-) create mode 100644 examples/tlsf/tests/forever.js diff --git a/examples/tlsf/assembly/tlsf.ts b/examples/tlsf/assembly/tlsf.ts index 41220b23..125f012f 100644 --- a/examples/tlsf/assembly/tlsf.ts +++ b/examples/tlsf/assembly/tlsf.ts @@ -107,7 +107,9 @@ class Block { // ├───────────────────────────────────────────────────────────────┤ │ // │ ... │ ◄────┤ // ├───────────────────────────────────────────────────────────────┤ │ -// │ head[736] │ ◄────┘ +// │ head[736] │ ◄────┤ +// ╞═══════════════════════════════════════════════════════════════╡ │ +// │ tailRef │ ◄────┘ // └───────────────────────────────────────────────────────────────┘ SIZE ┘ // S: Small blocks map, P: Possibly padded if 64-bit @@ -164,11 +166,17 @@ class Root { , Root.HL_START); } - /** Total size of the {@link Root} structure. */ - static readonly SIZE: usize = ( + /** End offset of FL/SL heads. */ + private static readonly HL_END: usize = ( Root.HL_START + FL_BITS * SL_SIZE * sizeof() ); + get tailRef(): usize { return load(0, Root.HL_END); } + set tailRef(value: usize) { store(0, value, Root.HL_END); } + + /** Total size of the {@link Root} structure. */ + static readonly SIZE: usize = Root.HL_END + sizeof(); + /** 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 @@ -363,12 +371,24 @@ class Root { /** Adds more memory to the pool. */ addMemory(start: usize, end: usize): bool { - start = (start + AL_MASK) & ~AL_MASK; - end -= end & AL_MASK; + assert(start <= end); + assert(!(start & AL_MASK)); // must be aligned + assert(!(end & AL_MASK)); // must be aligned - // TODO: merge with current tail if adjacent + var tailRef = this.tailRef; + var tailInfo: usize = 0; + if (tailRef) { + assert(start >= tailRef + sizeof()); // starts after tail - assert(start <= end); // to be sure + // merge with current tail if adjacent + if (start - Block.INFO == tailRef) { + start -= Block.INFO; + let tail = changetype(tailRef); + tailInfo = tail.info; + } + + } else + assert(start >= changetype(this) + Root.SIZE); // starts after root // check if size is large enough for a free block and the tail block var size = end - start; @@ -378,13 +398,14 @@ class Root { // left size is total minus its own and the zero-length tail's header var leftSize = size - 2 * Block.INFO; var left = changetype(start); - left.info = leftSize | FREE; + left.info = leftSize | FREE | (tailInfo & LEFT_FREE); left.prev = null; left.next = null; // tail is a zero-length used block var tail = changetype(start + size - Block.INFO); tail.info = 0 | LEFT_FREE; + this.tailRef = changetype(tail); this.insert(left); // also sets jump @@ -418,6 +439,7 @@ export function allocate_memory(size: usize): usize { if (!root) { var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK; ROOT = root = changetype(rootOffset); + root.tailRef = 0; root.flMap = 0; for (var fl: usize = 0; fl < FL_BITS; ++fl) { root.setSLMap(fl, 0); @@ -465,4 +487,4 @@ export function free_memory(data: usize): void { } // For stand-alone usage, e.g., in tests -export { move_memory, set_memory, compare_memory }; +// export { move_memory, set_memory, compare_memory }; diff --git a/examples/tlsf/package.json b/examples/tlsf/package.json index 9711f994..1e4a2e42 100644 --- a/examples/tlsf/package.json +++ b/examples/tlsf/package.json @@ -6,6 +6,7 @@ "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 -O3 assembly/tlsf.ts -b tlsf.optimized.wasm -t tlsf.optimized.wast --validate --noDebug --noAssert --sourceMap --measure", - "test": "node tests" + "test": "node tests", + "test:forever": "node tests/forever" } } diff --git a/examples/tlsf/tests/forever.js b/examples/tlsf/tests/forever.js new file mode 100644 index 00000000..ac1d2e71 --- /dev/null +++ b/examples/tlsf/tests/forever.js @@ -0,0 +1,13 @@ +var child_process = require("child_process"); + +// restarts the test forever, that is, until an issue is detected + +var count = 0; +while (true) { + console.log("[ #" + ++count + " ]\n"); + var res = child_process.spawnSync("node", [ "tests" ], { stdio: "inherit" }); + if (res.status !== 0) + throw Error("exited with " + res.status); + if (res.error) + throw res.error; +} diff --git a/examples/tlsf/tests/index.js b/examples/tlsf/tests/index.js index 0d0aaf5b..83b56350 100644 --- a/examples/tlsf/tests/index.js +++ b/examples/tlsf/tests/index.js @@ -20,7 +20,7 @@ function test(file) { return String.fromCharCode.apply(String, str); } - runner(exports, 50, 20000); // picked so I/O isn't the bottleneck + runner(exports, 5, 20000); // picked so I/O isn't the bottleneck console.log("mem final: " + exports.memory.buffer.byteLength); console.log(); } diff --git a/examples/tlsf/tests/runner.js b/examples/tlsf/tests/runner.js index efe22060..d85e2caf 100644 --- a/examples/tlsf/tests/runner.js +++ b/examples/tlsf/tests/runner.js @@ -37,35 +37,36 @@ function runner(tlsf, runs, allocs) { tlsf.free_memory(base); console.log("mem initial: " + tlsf.memory.buffer.byteLength); - for (var j = 0; j < runs; ++j) { - console.log("run " + (j + 1) + " (" + allocs + " allocations) ..."); - for (var i = 0; i < allocs; ++i) { - var ptr = randomAlloc(); + try { + for (var j = 0; j < runs; ++j) { + console.log("run " + (j + 1) + " (" + allocs + " allocations) ..."); + for (var i = 0; i < allocs; ++i) { + var ptr = randomAlloc(); - // immediately free every 4th - if (!(i % 4)) preciseFree(ptr); + // immediately free every 4th + if (!(i % 4)) preciseFree(ptr); - // occasionally free random blocks - else if (ptrs.length && Math.random() < 0.33) randomFree(); + // occasionally free random blocks + else if (ptrs.length && Math.random() < 0.33) randomFree(); - // ^ sums up to clearing about half the blocks half-way + // ^ sums up to clearing about half the blocks half-way + } + // free the rest, randomly + while (ptrs.length) randomFree(); + + // should now be possible to reuse the entire memory + // just try a large portion of the memory here because of SL+1 for allocs + var size = ((tlsf.memory.buffer.byteLength - base) * 9 / 10) >>> 0; + var ptr = tlsf.allocate_memory(size); + if (tlsf.set_memory) + tlsf.set_memory(ptr, 0xac, size); + if (ptr !== base) + throw Error("expected " + base + " but got " + ptr); + tlsf.free_memory(ptr); } - // free the rest, randomly - while (ptrs.length) randomFree(); - - // should now be possible to reuse the entire first page (remember: sl+1) - // e.g. with base 3088 (3048 optimized due to static memory): - var size = 0x10000 - base - 4 - 1008; - // 61436 (1110111111111100b) -> fl = 15, sl = 27 - // 61437 (61440 aligned, 1111000000000000b) -> fl = 15, sl = 28 - // NOTE that this calculation will be different if static memory changes - var ptr = tlsf.allocate_memory(size); - tlsf.set_memory(ptr, 0xac, size); - if (ptr !== base) throw Error("expected " + base + " but got " + ptr); - tlsf.free_memory(ptr); + } finally { + // mem(tlsf.memory, 0, 0x10000); } - - mem(tlsf.memory, 0, 0x10000); // should end in 02 00 00 00 (tail LEFT_FREE) } function mem(memory, offset, count) { diff --git a/src/types.ts b/src/types.ts index 19b2384d..a413a120 100644 --- a/src/types.ts +++ b/src/types.ts @@ -314,35 +314,124 @@ export class Type { // Types /** An 8-bit signed integer. */ - static readonly i8: Type = new Type(TypeKind.I8, TypeFlags.SIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 8); + static readonly i8: Type = new Type(TypeKind.I8, + TypeFlags.SIGNED | + TypeFlags.SMALL | + TypeFlags.INTEGER | + TypeFlags.VALUE, 8 + ); + /** A 16-bit signed integer. */ - static readonly i16: Type = new Type(TypeKind.I16, TypeFlags.SIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 16); + static readonly i16: Type = new Type(TypeKind.I16, + TypeFlags.SIGNED | + TypeFlags.SMALL | + TypeFlags.INTEGER | + TypeFlags.VALUE, 16 + ); + /** A 32-bit signed integer. */ - static readonly i32: Type = new Type(TypeKind.I32, TypeFlags.SIGNED | TypeFlags.INTEGER | TypeFlags.VALUE, 32); + static readonly i32: Type = new Type(TypeKind.I32, + TypeFlags.SIGNED | + TypeFlags.INTEGER | + TypeFlags.VALUE, 32 + ); + /** A 64-bit signed integer. */ - static readonly i64: Type = new Type(TypeKind.I64, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.INTEGER | TypeFlags.VALUE, 64); + static readonly i64: Type = new Type(TypeKind.I64, + TypeFlags.SIGNED | + TypeFlags.LONG | + TypeFlags.INTEGER | + TypeFlags.VALUE, 64 + ); + /** A 32-bit signed size. WASM32 only. */ - static readonly isize32: Type = new Type(TypeKind.ISIZE, TypeFlags.SIGNED | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 32); + static readonly isize32: Type = new Type(TypeKind.ISIZE, + TypeFlags.SIGNED | + TypeFlags.SIZE | + TypeFlags.INTEGER | + TypeFlags.VALUE, 32 + ); + /** A 64-bit signed size. WASM64 only. */ - static readonly isize64: Type = new Type(TypeKind.ISIZE, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 64); + static readonly isize64: Type = new Type(TypeKind.ISIZE, + TypeFlags.SIGNED | + TypeFlags.LONG | + TypeFlags.SIZE | + TypeFlags.INTEGER | + TypeFlags.VALUE, 64 + ); + /** An 8-bit unsigned integer. */ - static readonly u8: Type = new Type(TypeKind.U8, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 8); + static readonly u8: Type = new Type(TypeKind.U8, + TypeFlags.UNSIGNED | + TypeFlags.SMALL | + TypeFlags.INTEGER | + TypeFlags.VALUE, 8 + ); + /** A 16-bit unsigned integer. */ - static readonly u16: Type = new Type(TypeKind.U16, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 16); + static readonly u16: Type = new Type(TypeKind.U16, + TypeFlags.UNSIGNED | + TypeFlags.SMALL | + TypeFlags.INTEGER | + TypeFlags.VALUE, 16 + ); + /** A 32-bit unsigned integer. */ - static readonly u32: Type = new Type(TypeKind.U32, TypeFlags.UNSIGNED | TypeFlags.INTEGER | TypeFlags.VALUE, 32); + static readonly u32: Type = new Type(TypeKind.U32, + TypeFlags.UNSIGNED | + TypeFlags.INTEGER | + TypeFlags.VALUE, 32 + ); + /** A 64-bit unsigned integer. */ - static readonly u64: Type = new Type(TypeKind.U64, TypeFlags.UNSIGNED | TypeFlags.LONG | TypeFlags.INTEGER | TypeFlags.VALUE, 64); + static readonly u64: Type = new Type(TypeKind.U64, + TypeFlags.UNSIGNED | + TypeFlags.LONG | + TypeFlags.INTEGER | + TypeFlags.VALUE, 64 + ); + /** A 32-bit unsigned size. WASM32 only. */ - static readonly usize32: Type = new Type(TypeKind.USIZE, TypeFlags.UNSIGNED | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 32); + static readonly usize32: Type = new Type(TypeKind.USIZE, + TypeFlags.UNSIGNED | + TypeFlags.SIZE | + TypeFlags.INTEGER | + TypeFlags.VALUE, 32 + ); + /** A 64-bit unsigned size. WASM64 only. */ - static readonly usize64: Type = new Type(TypeKind.USIZE, TypeFlags.UNSIGNED | TypeFlags.LONG | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 64); + static readonly usize64: Type = new Type(TypeKind.USIZE, + TypeFlags.UNSIGNED | + TypeFlags.LONG | + TypeFlags.SIZE | + TypeFlags.INTEGER | + TypeFlags.VALUE, 64 + ); + /** A 1-bit unsigned integer. */ - static readonly bool: Type = new Type(TypeKind.BOOL, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 1); + static readonly bool: Type = new Type(TypeKind.BOOL, + TypeFlags.UNSIGNED | + TypeFlags.SMALL | + TypeFlags.INTEGER | + TypeFlags.VALUE, 1 + ); + /** A 32-bit float. */ - static readonly f32: Type = new Type(TypeKind.F32, TypeFlags.SIGNED | TypeFlags.FLOAT | TypeFlags.VALUE, 32); + static readonly f32: Type = new Type(TypeKind.F32, + TypeFlags.SIGNED | + TypeFlags.FLOAT | + TypeFlags.VALUE, 32 + ); + /** A 64-bit float. */ - static readonly f64: Type = new Type(TypeKind.F64, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.FLOAT | TypeFlags.VALUE, 64); + static readonly f64: Type = new Type(TypeKind.F64, + TypeFlags.SIGNED | + TypeFlags.LONG | + TypeFlags.FLOAT | + TypeFlags.VALUE, 64 + ); + /** No return type. */ static readonly void: Type = new Type(TypeKind.VOID, TypeFlags.NONE, 0); } @@ -356,7 +445,7 @@ export function typesToNativeTypes(types: Type[]): NativeType[] { return ret; } -/** Converts an array of types to its combined string representation. Usually type arguments. */ +/** Converts an array of types to its combined string representation. */ export function typesToString(types: Type[]): string { var k = types.length; if (!k)