Skip to content

Commit a7e815d

Browse files
committed
TLSF: Coalesce pages by extending the tail block, see #15
1 parent 41c0f2c commit a7e815d

File tree

6 files changed

+178
-52
lines changed

6 files changed

+178
-52
lines changed

examples/tlsf/assembly/tlsf.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ class Block {
107107
// ├───────────────────────────────────────────────────────────────┤ │
108108
// │ ... │ ◄────┤
109109
// ├───────────────────────────────────────────────────────────────┤ │
110-
// │ head[736] │ ◄────┘
110+
// │ head[736] │ ◄────┤
111+
// ╞═══════════════════════════════════════════════════════════════╡ │
112+
// │ tailRef │ ◄────┘
111113
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
112114
// S: Small blocks map, P: Possibly padded if 64-bit
113115

@@ -164,11 +166,17 @@ class Root {
164166
, Root.HL_START);
165167
}
166168

167-
/** Total size of the {@link Root} structure. */
168-
static readonly SIZE: usize = (
169+
/** End offset of FL/SL heads. */
170+
private static readonly HL_END: usize = (
169171
Root.HL_START + FL_BITS * SL_SIZE * sizeof<usize>()
170172
);
171173

174+
get tailRef(): usize { return load<usize>(0, Root.HL_END); }
175+
set tailRef(value: usize) { store<usize>(0, value, Root.HL_END); }
176+
177+
/** Total size of the {@link Root} structure. */
178+
static readonly SIZE: usize = Root.HL_END + sizeof<usize>();
179+
172180
/** Inserts a previously used block back into the free list. */
173181
insert(block: Block): void {
174182
// check as much as possible here to prevent invalid free blocks
@@ -363,12 +371,24 @@ class Root {
363371

364372
/** Adds more memory to the pool. */
365373
addMemory(start: usize, end: usize): bool {
366-
start = (start + AL_MASK) & ~AL_MASK;
367-
end -= end & AL_MASK;
368-
369-
// TODO: merge with current tail if adjacent
374+
assert(start <= end);
375+
assert(!(start & AL_MASK)); // must be aligned
376+
assert(!(end & AL_MASK)); // must be aligned
377+
378+
var tailRef = this.tailRef;
379+
var tailInfo: usize = 0;
380+
if (tailRef) {
381+
assert(start >= tailRef + sizeof<usize>()); // starts after tail
382+
383+
// merge with current tail if adjacent
384+
if (start - Block.INFO == tailRef) {
385+
start -= Block.INFO;
386+
let tail = changetype<Block>(tailRef);
387+
tailInfo = tail.info;
388+
}
370389

371-
assert(start <= end); // to be sure
390+
} else
391+
assert(start >= changetype<usize>(this) + Root.SIZE); // starts after root
372392

373393
// check if size is large enough for a free block and the tail block
374394
var size = end - start;
@@ -378,13 +398,14 @@ class Root {
378398
// left size is total minus its own and the zero-length tail's header
379399
var leftSize = size - 2 * Block.INFO;
380400
var left = changetype<Block>(start);
381-
left.info = leftSize | FREE;
401+
left.info = leftSize | FREE | (tailInfo & LEFT_FREE);
382402
left.prev = null;
383403
left.next = null;
384404

385405
// tail is a zero-length used block
386406
var tail = changetype<Block>(start + size - Block.INFO);
387407
tail.info = 0 | LEFT_FREE;
408+
this.tailRef = changetype<usize>(tail);
388409

389410
this.insert(left); // also sets jump
390411

@@ -418,6 +439,7 @@ export function allocate_memory(size: usize): usize {
418439
if (!root) {
419440
var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
420441
ROOT = root = changetype<Root>(rootOffset);
442+
root.tailRef = 0;
421443
root.flMap = 0;
422444
for (var fl: usize = 0; fl < FL_BITS; ++fl) {
423445
root.setSLMap(fl, 0);
@@ -465,4 +487,4 @@ export function free_memory(data: usize): void {
465487
}
466488

467489
// For stand-alone usage, e.g., in tests
468-
export { move_memory, set_memory, compare_memory };
490+
// export { move_memory, set_memory, compare_memory };

examples/tlsf/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"build": "npm run build:untouched && npm run build:optimized",
77
"build:untouched": "asc assembly/tlsf.ts -t tlsf.untouched.wast -b tlsf.untouched.wasm --validate --sourceMap --measure",
88
"build:optimized": "asc -O3 assembly/tlsf.ts -b tlsf.optimized.wasm -t tlsf.optimized.wast --validate --noDebug --noAssert --sourceMap --measure",
9-
"test": "node tests"
9+
"test": "node tests",
10+
"test:forever": "node tests/forever"
1011
}
1112
}

examples/tlsf/tests/forever.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var child_process = require("child_process");
2+
3+
// restarts the test forever, that is, until an issue is detected
4+
5+
var count = 0;
6+
while (true) {
7+
console.log("[ #" + ++count + " ]\n");
8+
var res = child_process.spawnSync("node", [ "tests" ], { stdio: "inherit" });
9+
if (res.status !== 0)
10+
throw Error("exited with " + res.status);
11+
if (res.error)
12+
throw res.error;
13+
}

examples/tlsf/tests/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function test(file) {
2020
return String.fromCharCode.apply(String, str);
2121
}
2222

23-
runner(exports, 50, 20000); // picked so I/O isn't the bottleneck
23+
runner(exports, 5, 20000); // picked so I/O isn't the bottleneck
2424
console.log("mem final: " + exports.memory.buffer.byteLength);
2525
console.log();
2626
}

examples/tlsf/tests/runner.js

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,36 @@ function runner(tlsf, runs, allocs) {
3737
tlsf.free_memory(base);
3838
console.log("mem initial: " + tlsf.memory.buffer.byteLength);
3939

40-
for (var j = 0; j < runs; ++j) {
41-
console.log("run " + (j + 1) + " (" + allocs + " allocations) ...");
42-
for (var i = 0; i < allocs; ++i) {
43-
var ptr = randomAlloc();
40+
try {
41+
for (var j = 0; j < runs; ++j) {
42+
console.log("run " + (j + 1) + " (" + allocs + " allocations) ...");
43+
for (var i = 0; i < allocs; ++i) {
44+
var ptr = randomAlloc();
4445

45-
// immediately free every 4th
46-
if (!(i % 4)) preciseFree(ptr);
46+
// immediately free every 4th
47+
if (!(i % 4)) preciseFree(ptr);
4748

48-
// occasionally free random blocks
49-
else if (ptrs.length && Math.random() < 0.33) randomFree();
49+
// occasionally free random blocks
50+
else if (ptrs.length && Math.random() < 0.33) randomFree();
5051

51-
// ^ sums up to clearing about half the blocks half-way
52-
}
53-
// free the rest, randomly
54-
while (ptrs.length) randomFree();
52+
// ^ sums up to clearing about half the blocks half-way
53+
}
54+
// free the rest, randomly
55+
while (ptrs.length) randomFree();
5556

56-
// should now be possible to reuse the entire first page (remember: sl+1)
57-
// e.g. with base 3088 (3048 optimized due to static memory):
58-
var size = 0x10000 - base - 4 - 1008;
59-
// 61436 (1110111111111100b) -> fl = 15, sl = 27
60-
// 61437 (61440 aligned, 1111000000000000b) -> fl = 15, sl = 28
61-
// NOTE that this calculation will be different if static memory changes
62-
var ptr = tlsf.allocate_memory(size);
63-
tlsf.set_memory(ptr, 0xac, size);
64-
if (ptr !== base) throw Error("expected " + base + " but got " + ptr);
65-
tlsf.free_memory(ptr);
57+
// should now be possible to reuse the entire memory
58+
// just try a large portion of the memory here because of SL+1 for allocs
59+
var size = ((tlsf.memory.buffer.byteLength - base) * 9 / 10) >>> 0;
60+
var ptr = tlsf.allocate_memory(size);
61+
if (tlsf.set_memory)
62+
tlsf.set_memory(ptr, 0xac, size);
63+
if (ptr !== base)
64+
throw Error("expected " + base + " but got " + ptr);
65+
tlsf.free_memory(ptr);
66+
}
67+
} finally {
68+
// mem(tlsf.memory, 0, 0x10000);
6669
}
67-
68-
mem(tlsf.memory, 0, 0x10000); // should end in 02 00 00 00 (tail LEFT_FREE)
6970
}
7071

7172
function mem(memory, offset, count) {

src/types.ts

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -314,35 +314,124 @@ export class Type {
314314
// Types
315315

316316
/** An 8-bit signed integer. */
317-
static readonly i8: Type = new Type(TypeKind.I8, TypeFlags.SIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 8);
317+
static readonly i8: Type = new Type(TypeKind.I8,
318+
TypeFlags.SIGNED |
319+
TypeFlags.SMALL |
320+
TypeFlags.INTEGER |
321+
TypeFlags.VALUE, 8
322+
);
323+
318324
/** A 16-bit signed integer. */
319-
static readonly i16: Type = new Type(TypeKind.I16, TypeFlags.SIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 16);
325+
static readonly i16: Type = new Type(TypeKind.I16,
326+
TypeFlags.SIGNED |
327+
TypeFlags.SMALL |
328+
TypeFlags.INTEGER |
329+
TypeFlags.VALUE, 16
330+
);
331+
320332
/** A 32-bit signed integer. */
321-
static readonly i32: Type = new Type(TypeKind.I32, TypeFlags.SIGNED | TypeFlags.INTEGER | TypeFlags.VALUE, 32);
333+
static readonly i32: Type = new Type(TypeKind.I32,
334+
TypeFlags.SIGNED |
335+
TypeFlags.INTEGER |
336+
TypeFlags.VALUE, 32
337+
);
338+
322339
/** A 64-bit signed integer. */
323-
static readonly i64: Type = new Type(TypeKind.I64, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.INTEGER | TypeFlags.VALUE, 64);
340+
static readonly i64: Type = new Type(TypeKind.I64,
341+
TypeFlags.SIGNED |
342+
TypeFlags.LONG |
343+
TypeFlags.INTEGER |
344+
TypeFlags.VALUE, 64
345+
);
346+
324347
/** A 32-bit signed size. WASM32 only. */
325-
static readonly isize32: Type = new Type(TypeKind.ISIZE, TypeFlags.SIGNED | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 32);
348+
static readonly isize32: Type = new Type(TypeKind.ISIZE,
349+
TypeFlags.SIGNED |
350+
TypeFlags.SIZE |
351+
TypeFlags.INTEGER |
352+
TypeFlags.VALUE, 32
353+
);
354+
326355
/** A 64-bit signed size. WASM64 only. */
327-
static readonly isize64: Type = new Type(TypeKind.ISIZE, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 64);
356+
static readonly isize64: Type = new Type(TypeKind.ISIZE,
357+
TypeFlags.SIGNED |
358+
TypeFlags.LONG |
359+
TypeFlags.SIZE |
360+
TypeFlags.INTEGER |
361+
TypeFlags.VALUE, 64
362+
);
363+
328364
/** An 8-bit unsigned integer. */
329-
static readonly u8: Type = new Type(TypeKind.U8, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 8);
365+
static readonly u8: Type = new Type(TypeKind.U8,
366+
TypeFlags.UNSIGNED |
367+
TypeFlags.SMALL |
368+
TypeFlags.INTEGER |
369+
TypeFlags.VALUE, 8
370+
);
371+
330372
/** A 16-bit unsigned integer. */
331-
static readonly u16: Type = new Type(TypeKind.U16, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 16);
373+
static readonly u16: Type = new Type(TypeKind.U16,
374+
TypeFlags.UNSIGNED |
375+
TypeFlags.SMALL |
376+
TypeFlags.INTEGER |
377+
TypeFlags.VALUE, 16
378+
);
379+
332380
/** A 32-bit unsigned integer. */
333-
static readonly u32: Type = new Type(TypeKind.U32, TypeFlags.UNSIGNED | TypeFlags.INTEGER | TypeFlags.VALUE, 32);
381+
static readonly u32: Type = new Type(TypeKind.U32,
382+
TypeFlags.UNSIGNED |
383+
TypeFlags.INTEGER |
384+
TypeFlags.VALUE, 32
385+
);
386+
334387
/** A 64-bit unsigned integer. */
335-
static readonly u64: Type = new Type(TypeKind.U64, TypeFlags.UNSIGNED | TypeFlags.LONG | TypeFlags.INTEGER | TypeFlags.VALUE, 64);
388+
static readonly u64: Type = new Type(TypeKind.U64,
389+
TypeFlags.UNSIGNED |
390+
TypeFlags.LONG |
391+
TypeFlags.INTEGER |
392+
TypeFlags.VALUE, 64
393+
);
394+
336395
/** A 32-bit unsigned size. WASM32 only. */
337-
static readonly usize32: Type = new Type(TypeKind.USIZE, TypeFlags.UNSIGNED | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 32);
396+
static readonly usize32: Type = new Type(TypeKind.USIZE,
397+
TypeFlags.UNSIGNED |
398+
TypeFlags.SIZE |
399+
TypeFlags.INTEGER |
400+
TypeFlags.VALUE, 32
401+
);
402+
338403
/** A 64-bit unsigned size. WASM64 only. */
339-
static readonly usize64: Type = new Type(TypeKind.USIZE, TypeFlags.UNSIGNED | TypeFlags.LONG | TypeFlags.SIZE | TypeFlags.INTEGER | TypeFlags.VALUE, 64);
404+
static readonly usize64: Type = new Type(TypeKind.USIZE,
405+
TypeFlags.UNSIGNED |
406+
TypeFlags.LONG |
407+
TypeFlags.SIZE |
408+
TypeFlags.INTEGER |
409+
TypeFlags.VALUE, 64
410+
);
411+
340412
/** A 1-bit unsigned integer. */
341-
static readonly bool: Type = new Type(TypeKind.BOOL, TypeFlags.UNSIGNED | TypeFlags.SMALL | TypeFlags.INTEGER | TypeFlags.VALUE, 1);
413+
static readonly bool: Type = new Type(TypeKind.BOOL,
414+
TypeFlags.UNSIGNED |
415+
TypeFlags.SMALL |
416+
TypeFlags.INTEGER |
417+
TypeFlags.VALUE, 1
418+
);
419+
342420
/** A 32-bit float. */
343-
static readonly f32: Type = new Type(TypeKind.F32, TypeFlags.SIGNED | TypeFlags.FLOAT | TypeFlags.VALUE, 32);
421+
static readonly f32: Type = new Type(TypeKind.F32,
422+
TypeFlags.SIGNED |
423+
TypeFlags.FLOAT |
424+
TypeFlags.VALUE, 32
425+
);
426+
344427
/** A 64-bit float. */
345-
static readonly f64: Type = new Type(TypeKind.F64, TypeFlags.SIGNED | TypeFlags.LONG | TypeFlags.FLOAT | TypeFlags.VALUE, 64);
428+
static readonly f64: Type = new Type(TypeKind.F64,
429+
TypeFlags.SIGNED |
430+
TypeFlags.LONG |
431+
TypeFlags.FLOAT |
432+
TypeFlags.VALUE, 64
433+
);
434+
346435
/** No return type. */
347436
static readonly void: Type = new Type(TypeKind.VOID, TypeFlags.NONE, 0);
348437
}
@@ -356,7 +445,7 @@ export function typesToNativeTypes(types: Type[]): NativeType[] {
356445
return ret;
357446
}
358447

359-
/** Converts an array of types to its combined string representation. Usually type arguments. */
448+
/** Converts an array of types to its combined string representation. */
360449
export function typesToString(types: Type[]): string {
361450
var k = types.length;
362451
if (!k)

0 commit comments

Comments
 (0)