mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-28 00:12:15 +00:00
TLSF: Coalesce pages by extending the tail block, see #15
This commit is contained in:
parent
41c0f2c6c3
commit
a7e815dcec
@ -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<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
|
||||
@ -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<usize>()); // 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<Block>(tailRef);
|
||||
tailInfo = tail.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;
|
||||
@ -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<Block>(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<Block>(start + size - Block.INFO);
|
||||
tail.info = 0 | LEFT_FREE;
|
||||
this.tailRef = changetype<usize>(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<Root>(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 };
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
13
examples/tlsf/tests/forever.js
Normal file
13
examples/tlsf/tests/forever.js
Normal file
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
121
src/types.ts
121
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user