refactor into stdlib

This commit is contained in:
dcode 2019-04-18 11:51:07 +02:00
parent ffdda4b695
commit 8216cf3361
14 changed files with 1378 additions and 1466 deletions

View File

@ -8378,6 +8378,66 @@ export class Compiler extends DiagnosticEmitter {
return stmts;
}
/** Wraps a reference in a `retain` call. Returns the reference if `tempLocal` is specified. */
makeRetain(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
var module = this.module;
var program = this.program;
var retainFn = assert(program.retainRef);
this.compileFunction(retainFn);
if (tempIndex >= 0) {
let nativeSizeType = this.options.nativeSizeType;
return module.createBlock(null, [
module.createCall(retainFn.internalName, [
module.createTeeLocal(tempIndex, valueExpr)
], NativeType.None),
module.createGetLocal(tempIndex, nativeSizeType)
], nativeSizeType);
} else {
return module.createCall(retainFn.internalName, [ valueExpr ], NativeType.None);
}
}
/** Wraps a reference in `release` call. Returns the reference if `tempLocal` is specified. */
makeRelease(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
var module = this.module;
var program = this.program;
var releaseFn = assert(program.releaseRef);
this.compileFunction(releaseFn);
if (tempIndex >= 0) {
let nativeSizeType = this.options.nativeSizeType;
return module.createBlock(null, [
module.createCall(releaseFn.internalName, [
module.createTeeLocal(tempIndex, valueExpr)
], NativeType.None),
module.createGetLocal(tempIndex, nativeSizeType)
], nativeSizeType);
} else {
return module.createCall(releaseFn.internalName, [ valueExpr ], NativeType.None);
}
}
/** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */
makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
// TODO: checking `newValue != oldValue` significantly reduces strain on the roots buffer
// when cyclic structures may be immediately released but also requires a tempIndex. might
// be worth to require a temp here. furthermore it might be worth to require it for retain
// and release as well so we can emit != null checks where necessary only?
var module = this.module;
if (tempIndex >= 0) {
let nativeSizeType = this.options.nativeSizeType;
return module.createBlock(null, [
this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr)),
this.makeRelease(oldValueExpr),
module.createGetLocal(tempIndex, nativeSizeType)
], nativeSizeType);
} else {
return module.createBlock(null, [
this.makeRetain(newValueExpr),
this.makeRelease(oldValueExpr)
]);
}
}
/** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
makeInsertRef(
valueExpr: ExpressionRef,

View File

@ -0,0 +1,6 @@
The AssemblyScript Runtime
==========================
The runtime provides the functionality necessary to dynamically allocate and deallocate memory of objects, arrays and buffers, as well as keep track of references that are no longer used.
It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counting garbage collector](./pure.ts).

13
std/assembly/rt/common.ts Normal file
View File

@ -0,0 +1,13 @@
// Alignment guarantees
// @ts-ignore: decorator
@inline export const AL_BITS: u32 = 4; // 16 bytes to fit up to v128
// @ts-ignore: decorator
@inline export const AL_SIZE: usize = 1 << <usize>AL_BITS;
// @ts-ignore: decorator
@inline export const AL_MASK: usize = AL_SIZE - 1;
// Extra debugging
// @ts-ignore: decorator
@inline export const DEBUG = true;

52
std/assembly/rt/index.ts Normal file
View File

@ -0,0 +1,52 @@
import { AL_MASK, DEBUG } from "./common";
import { ROOT, Block, BLOCK_OVERHEAD, initializeRoot, allocateBlock, reallocateBlock, freeBlock } from "./tlsf";
import { increment, decrement, collectCycles } from "./pure";
//////////////////////////////////// Memory manager interface /////////////////////////////////////
// @ts-ignore: decorator
@global @unsafe
function __mm_allocate(size: usize): usize {
var root = ROOT;
if (!root) {
initializeRoot();
root = ROOT;
}
return changetype<usize>(allocateBlock(root, size)) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
function __mm_reallocate(data: usize, size: usize): usize {
if (DEBUG) assert(ROOT); // must be initialized
assert(data != 0 && !(data & AL_MASK)); // must exist and be aligned
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(data - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
function __mm_free(data: usize): void {
if (DEBUG) assert(ROOT); // must be initialized
assert(data != 0 && !(data & AL_MASK)); // must exist and be aligned
freeBlock(ROOT, changetype<Block>(data - BLOCK_OVERHEAD));
}
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
// @ts-ignore: decorator
@global @unsafe
function __gc_retain(ref: usize): void {
if (ref) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
function __gc_release(ref: usize): void {
if (ref) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
function __gc_collect(): void {
collectCycles();
}

239
std/assembly/rt/pure.ts Normal file
View File

@ -0,0 +1,239 @@
import { DEBUG } from "./common";
import { Block, freeBlock, ROOT } from "./tlsf";
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
// TODO: make visitors eat cookies so we can compile direct calls into a switch
function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); }
function __rt_flags(classId: u32): u32 { return unreachable(); }
const ACYCLIC_FLAG: u32 = 0;
// ╒══════════════════════ GC Info structure ══════════════════════╕
// │ 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│
// ├─┼─┴─┴─┼─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
// │B│color│ refCount │
// └─┴─────┴───────────────────────────────────────────────────────┘
// B: buffered
// @ts-ignore: decorator
@inline const BUFFERED_MASK: u32 = 1 << (sizeof<u32>() * 8 - 1);
// @ts-ignore: decorator
@inline const COLOR_BITS = 3;
// @ts-ignore: decorator
@inline const COLOR_SHIFT: u32 = ctz(BUFFERED_MASK) - COLOR_BITS;
// @ts-ignore: decorator
@inline const COLOR_MASK: u32 = ((1 << COLOR_BITS) - 1) << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const REFCOUNT_MASK: u32 = (1 << COLOR_SHIFT) - 1;
// ╒════════╤═══════════════════ Colors ═══════════════════════════╕
// │ Color │ Meaning │
// ├────────┼──────────────────────────────────────────────────────┤
// │ BLACK │ In use or free │
// │ GRAY │ Possible member of cycle │
// │ WHITE │ Member of garbage cycle │
// │ PURPLE │ Possible root of cycle │
// │ RED │ Candidate cycle undergoing Σ-computation *concurrent │
// │ ORANGE │ Candidate cycle awaiting epoch boundary *concurrent │
// └────────┴──────────────────────────────────────────────────────┘
// Acyclic detection has been decoupled, hence no GREEN.
// @ts-ignore: decorator
@inline const COLOR_BLACK: u32 = 0 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_GRAY: u32 = 1 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_WHITE: u32 = 2 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_PURPLE: u32 = 3 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_RED: u32 = 4 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_ORANGE: u32 = 5 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const VISIT_DECREMENT = 1; // guard 0
// @ts-ignore: decorator
@inline const VISIT_MARKGRAY = 2;
// @ts-ignore: decorator
@inline const VISIT_SCAN = 3;
// @ts-ignore: decorator
@inline const VISIT_SCANBLACK = 4;
// @ts-ignore: decorator
@inline const VISIT_COLLECTWHITE = 5;
// @ts-ignore: decorator
@global
function __rt_visit(s: Block, cookie: i32): void {
switch (cookie) {
case VISIT_DECREMENT: {
decrement(s);
break;
}
case VISIT_MARKGRAY: {
if (DEBUG) assert((s.gcInfo & REFCOUNT_MASK) > 0);
s.gcInfo = s.gcInfo - 1;
markGray(s);
break;
}
case VISIT_SCAN: {
scan(s);
break;
}
case VISIT_SCANBLACK: {
let info = s.gcInfo;
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
s.gcInfo = info + 1;
if ((info & COLOR_MASK) != COLOR_BLACK) {
scanBlack(s);
}
break;
}
case VISIT_COLLECTWHITE: {
collectWhite(s);
break;
}
default: if (DEBUG) assert(false);
}
}
/** Increments the reference count of the specified block by one.*/
export function increment(s: Block): void {
var info = s.gcInfo;
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
s.gcInfo = info + 1;
}
/** Decrements the reference count of the specified block by one, possibly freeing it. */
export function decrement(s: Block): void {
var info = s.gcInfo;
var rc = info & REFCOUNT_MASK;
if (rc == 1) {
__rt_visit_members(s, VISIT_DECREMENT);
if (!(info & BUFFERED_MASK)) {
freeBlock(ROOT, s);
} else {
s.gcInfo = BUFFERED_MASK | COLOR_BLACK | 0;
}
} else {
if (DEBUG) assert(rc > 0);
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
if (!(info & BUFFERED_MASK)) {
appendRoot(s);
}
} else {
s.gcInfo = (info & ~REFCOUNT_MASK) | (rc - 1);
}
}
}
/** Buffer of possible roots. */
// @ts-ignore: decorator
@lazy var ROOTS: usize;
/** Current absolute offset into the `ROOTS` buffer. */
// @ts-ignore: decorator
@lazy var CUR: usize = 0;
/** Current absolute end offset into the `ROOTS` buffer. */
// @ts-ignore: decorator
@lazy var END: usize = 0;
/** Appends a block to possible roots. */
function appendRoot(s: Block): void {
var cur = CUR;
if (cur >= END) {
growRoots(); // TBD: either that or pick a default and force collection on overflow
cur = CUR;
}
store<Block>(cur, s);
CUR = cur + 1;
}
/** Grows the roots buffer if it ran full. */
function growRoots(): void {
var oldRoots = ROOTS;
var oldSize = CUR - oldRoots;
var newSize = max(oldSize * 2, 64 << alignof<usize>());
var newRoots = memory.allocate(newSize);
memory.copy(newRoots, oldRoots, oldSize);
ROOTS = newRoots;
CUR = newRoots + oldSize;
END = newRoots + newSize;
}
/** Collects cyclic garbage. */
export function collectCycles(): void {
// markRoots
var roots = ROOTS;
var cur = roots;
for (let pos = cur, end = CUR; pos < end; pos += sizeof<usize>()) {
let s = load<Block>(pos);
let info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_PURPLE && (info & REFCOUNT_MASK) > 0) {
markGray(s);
store<Block>(cur, s);
cur += sizeof<usize>();
} else {
if ((info & COLOR_MASK) == COLOR_BLACK && !(info & REFCOUNT_MASK)) {
freeBlock(ROOT, s);
} else {
s.gcInfo = info & ~BUFFERED_MASK;
}
}
}
CUR = cur;
// scanRoots
for (let pos = roots; pos < cur; pos += sizeof<usize>()) {
scan(load<Block>(pos));
}
// collectRoots
for (let pos = roots; pos < cur; pos += sizeof<usize>()) {
let s = load<Block>(pos);
s.gcInfo = s.gcInfo & ~BUFFERED_MASK;
collectWhite(s);
}
CUR = roots;
}
/** Marks a block as gray (possible member of cycle) during the collection phase. */
function markGray(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) != COLOR_GRAY) {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
__rt_visit_members(s, VISIT_MARKGRAY);
}
}
/** Scans a block during the collection phase, determining whether it is garbage or not. */
function scan(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_GRAY) {
if ((info & REFCOUNT_MASK) > 0) {
scanBlack(s);
} else {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
__rt_visit_members(s, VISIT_SCAN);
}
}
}
/** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
function scanBlack(s: Block): void {
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_SCANBLACK);
}
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
function collectWhite(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_COLLECTWHITE);
}
freeBlock(ROOT, s);
}

522
std/assembly/rt/tlsf.ts Normal file
View File

@ -0,0 +1,522 @@
import { AL_BITS, AL_SIZE, AL_MASK, DEBUG } from "./common";
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
// see: http://www.gii.upv.es/tlsf/
// - `ffs(x)` is equivalent to `ctz(x)` with x != 0
// - `fls(x)` is equivalent to `sizeof(x) * 8 - clz(x) - 1`
// ╒══════════════ 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
// @ts-ignore: decorator
@inline const SL_BITS: u32 = 4;
// @ts-ignore: decorator
@inline const SL_SIZE: usize = 1 << <usize>SL_BITS;
// @ts-ignore: decorator
@inline const SB_BITS: usize = <usize>(SL_BITS + AL_BITS);
// @ts-ignore: decorator
@inline const SB_SIZE: usize = 1 << <usize>SB_BITS;
// @ts-ignore: decorator
@inline const FL_BITS: u32 = 31 - SB_BITS;
// [00]: < 256B (SB) [12]: < 1M
// [01]: < 512B [13]: < 2M
// [02]: < 1K [14]: < 4M
// [03]: < 2K [15]: < 8M
// [04]: < 4K [16]: < 16M
// [05]: < 8K [17]: < 32M
// [06]: < 16K [18]: < 64M
// [07]: < 32K [19]: < 128M
// [08]: < 64K [20]: < 256M
// [09]: < 128K [21]: < 512M
// [10]: < 256K [22]: <= 1G - OVERHEAD
// [11]: < 512K
// VMs limit to 2GB total (currently), making one 1G block max (or three 512M etc.) due to block overhead
// Tags stored in otherwise unused alignment bits
// @ts-ignore: decorator
@inline const FREE: usize = 1 << 0;
// @ts-ignore: decorator
@inline const LEFTFREE: usize = 1 << 1;
// @ts-ignore: decorator
@inline const TAGS_MASK: usize = FREE | LEFTFREE; // <= AL_MASK
// ╒════════════════════ Block 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┼─┤ overhead ┐
// │ size │0│L│F│ ◄─┐ info
// ├─────────────────────────────────────────────────────────┴─┴─┴─┤ │
// │ │ │
// │ ... additional runtime overhead ... │ │
// │ │ │
// ╞═══════════════════════════════════════════════════════════════╡ │ ┐ ┘
// │ if free: ◄ prev │ ◄─┤ usize
// ├───────────────────────────────────────────────────────────────┤ │
// │ if free: next ► │ ◄─┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ ... │ │ = 0
// ├───────────────────────────────────────────────────────────────┤ │
// │ if free: back ▲ │ ◄─┘
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
// F: FREE, L: LEFTFREE
@unmanaged export class Block {
/** Memory manager info. */
mmInfo: usize; // WASM64 might need adaption
/** Garbage collector info. */
gcInfo: u32;
/** Runtime class id. */
rtId: u32;
/** Runtime object size. */
rtSize: u32;
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
prev: Block | null;
/** Next free block, if any. Only valid if free, otherwise part of payload. */
next: Block | null;
// If the block is free, there is a 'back'reference at its end pointing at its start.
}
// Block constants. Overhead is always present, no matter if free or used. Also, a block must have
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free.
// @ts-ignore: decorator
@inline export const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back
// @ts-ignore: decorator
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
/** Gets the left block of a block. Only valid if the left block is free. */
// @ts-ignore: decorator
@inline function GETFREELEFT(block: Block): Block {
return load<Block>(changetype<usize>(block) - sizeof<usize>());
}
/** Gets the right block of of a block by advancing to the right by its size. */
// @ts-ignore: decorator
@inline function GETRIGHT(block: Block): Block {
return changetype<Block>(changetype<usize>(block) + BLOCK_OVERHEAD + (block.mmInfo & ~TAGS_MASK));
}
// ╒═════════════════════ Root 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] │ ◄─┘ │
// ╞═══════════════════════════════════════════════════════════════╡ usize
// │ head[0] │ ◄────┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ ... │ ◄────┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ head[367] │ ◄────┤
// ╞═══════════════════════════════════════════════════════════════╡ │
// │ tail │ ◄────┘
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
// S: Small blocks map
@unmanaged class Root {
/** First level bitmap. */
flMap: usize;
}
// Root constants. Where stuff is stored inside of the root structure.
// @ts-ignore: decorator
@inline const SL_START = sizeof<usize>();
// @ts-ignore: decorator
@inline const SL_END = SL_START + (FL_BITS << alignof<u32>());
// @ts-ignore: decorator
@inline const HL_START = (SL_END + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@inline const HL_END = HL_START + FL_BITS * SL_SIZE * sizeof<usize>();
// @ts-ignore: decorator
@inline const ROOT_SIZE = HL_END + sizeof<usize>();
// @ts-ignore: decorator
@lazy export var ROOT: Root;
/** Gets the second level map of the specified first level. */
// @ts-ignore: decorator
@inline function GETSL(root: Root, fl: usize): u32 {
return load<u32>(changetype<usize>(root) + (fl << alignof<u32>()), SL_START);
}
/** Sets the second level map of the specified first level. */
// @ts-ignore: decorator
@inline function SETSL(root: Root, fl: usize, slMap: u32): void {
store<u32>(changetype<usize>(root) + (fl << alignof<u32>()), slMap, SL_START);
}
/** Gets the head of the free list for the specified combination of first and second level. */
// @ts-ignore: decorator
@inline function GETHEAD(root: Root, fl: usize, sl: u32): Block | null {
return changetype<Block>(load<usize>(changetype<usize>(root) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>(), HL_START));
}
/** Sets the head of the free list for the specified combination of first and second level. */
// @ts-ignore: decorator
@inline function SETHEAD(root: Root, fl: usize, sl: u32, head: Block | null): void {
store<usize>(changetype<usize>(root) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>() , changetype<usize>(head), HL_START);
}
/** Gets the tail block.. */
// @ts-ignore: decorator
@inline function GETTAIL(root: Root): Block {
return load<Block>(changetype<usize>(root), HL_END);
}
/** Sets the tail block. */
// @ts-ignore: decorator
@inline function SETTAIL(root: Root, tail: Block): void {
store<Block>(changetype<usize>(root), tail, HL_END);
}
/** Inserts a previously used block back into the free list. */
function insertBlock(root: Root, block: Block): void {
if (DEBUG) assert(block); // cannot be null
var blockInfo = block.mmInfo;
if (DEBUG) assert(blockInfo & FREE); // must be free
var right = GETRIGHT(block);
var rightInfo = right.mmInfo;
// merge with right block if also free
if (rightInfo & FREE) {
let newSize = (blockInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (rightInfo & ~TAGS_MASK);
if (newSize < BLOCK_MAXSIZE) {
removeBlock(root, right);
block.mmInfo = blockInfo = (blockInfo & TAGS_MASK) | newSize;
right = GETRIGHT(block);
rightInfo = right.mmInfo;
// 'back' is set below
}
}
// merge with left block if also free
if (blockInfo & LEFTFREE) {
let left = GETFREELEFT(block);
let leftInfo = left.mmInfo;
if (DEBUG) assert(leftInfo & FREE); // must be free according to right tags
let newSize = (leftInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (blockInfo & ~TAGS_MASK);
if (newSize < BLOCK_MAXSIZE) {
removeBlock(root, left);
left.mmInfo = blockInfo = (leftInfo & TAGS_MASK) | newSize;
block = left;
// 'back' is set below
}
}
right.mmInfo = rightInfo | LEFTFREE;
// right is no longer used now, hence rightInfo is not synced
// we now know the size of the block
var size = blockInfo & ~TAGS_MASK;
if (DEBUG) assert(size >= BLOCK_MINSIZE && size < BLOCK_MAXSIZE); // must be a valid size
if (DEBUG) assert(changetype<usize>(block) + BLOCK_OVERHEAD + size == changetype<usize>(right)); // must match
// set 'back' to itself at the end of block
store<Block>(changetype<usize>(right) - sizeof<usize>(), block);
// mapping_insert
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
const inv: usize = sizeof<usize>() * 8 - 1;
fl = inv - clz<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
// perform insertion
var head = GETHEAD(root, fl, sl);
block.prev = null;
block.next = head;
if (head) head.prev = block;
SETHEAD(root, fl, sl, block);
// update first and second level maps
root.flMap |= (1 << fl);
SETSL(root, fl, GETSL(root, fl) | (1 << sl));
}
/** Removes a free block from internal lists. */
function removeBlock(root: Root, block: Block): void {
var blockInfo = block.mmInfo;
if (DEBUG) assert(blockInfo & FREE); // must be free
var size = blockInfo & ~TAGS_MASK;
if (DEBUG) assert(size >= BLOCK_MINSIZE && size < BLOCK_MAXSIZE); // must be valid
// mapping_insert
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
const inv: usize = sizeof<usize>() * 8 - 1;
fl = inv - clz<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
// 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 == GETHEAD(root, fl, sl)) {
SETHEAD(root, fl, sl, next);
// clear second level map if head is empty now
if (!next) {
let slMap = GETSL(root, fl);
SETSL(root, fl, slMap &= ~(1 << sl));
// clear first level map if second level is empty now
if (!slMap) root.flMap &= ~(1 << fl);
}
}
// note: does not alter left/back because it is likely that splitting
// is performed afterwards, invalidating those changes. so, the caller
// must perform those updates.
}
/** Searches for a free block of at least the specified size. */
function searchBlock(root: Root, size: usize): Block | null {
// size was already asserted by caller
// mapping_search
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
const halfMaxSize = BLOCK_MAXSIZE >> 1; // don't round last fl
const inv: usize = sizeof<usize>() * 8 - 1;
const invRound = inv - SL_BITS;
let requestSize = size < halfMaxSize
? size + (1 << (invRound - clz<usize>(size))) - 1
: size;
fl = inv - clz<usize>(requestSize);
sl = <u32>((requestSize >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
// search second level
var slMap = GETSL(root, fl) & (~0 << sl);
var head: Block | null;
if (!slMap) {
// search next larger first level
let flMap = root.flMap & (~0 << (fl + 1));
if (!flMap) {
head = null;
} else {
fl = ctz<usize>(flMap);
slMap = GETSL(root, fl);
if (DEBUG) assert(slMap); // can't be zero if fl points here
head = GETHEAD(root, fl, ctz<u32>(slMap));
}
} else {
head = GETHEAD(root, fl, ctz<u32>(slMap));
}
return head;
}
/** Prepares the specified block before (re-)use, possibly splitting it. */
function prepareBlock(root: Root, block: Block, size: usize): void {
// size was already asserted by caller
var blockInfo = block.mmInfo;
if (DEBUG) assert(!(size & AL_MASK)); // size must be aligned so the new block is
// split if the block can hold another MINSIZE block incl. overhead
var remaining = (blockInfo & ~TAGS_MASK) - size;
if (remaining >= BLOCK_OVERHEAD + BLOCK_MINSIZE) {
block.mmInfo = size | (blockInfo & LEFTFREE); // also discards FREE
let spare = changetype<Block>(changetype<usize>(block) + BLOCK_OVERHEAD + size);
spare.mmInfo = (remaining - BLOCK_OVERHEAD) | FREE; // not LEFTFREE
insertBlock(root, spare); // also sets 'back'
// otherwise tag block as no longer FREE and right as no longer LEFTFREE
} else {
block.mmInfo = blockInfo & ~FREE;
GETRIGHT(block).mmInfo &= ~LEFTFREE;
}
}
/** Adds more memory to the pool. */
function addMemory(root: Root, start: usize, end: usize): bool {
if (DEBUG) {
assert(
start <= end && // must be valid
!(start & AL_MASK) && // must be aligned
!(end & AL_MASK) // must be aligned
);
}
var tail = GETTAIL(root);
var tailInfo: usize = 0;
if (tail) { // more memory
if (DEBUG) assert(start >= changetype<usize>(tail) + BLOCK_OVERHEAD);
// merge with current tail if adjacent
if (start - BLOCK_OVERHEAD == changetype<usize>(tail)) {
start -= BLOCK_OVERHEAD;
tailInfo = tail.mmInfo;
} else {
// We don't do this, but a user might `memory.grow` manually
// leading to non-adjacent pages managed by TLSF.
}
} else if (DEBUG) { // first memory
assert(start >= changetype<usize>(root) + 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_OVERHEAD + BLOCK_MINSIZE + BLOCK_OVERHEAD) {
return false;
}
// left size is total minus its own and the zero-length tail's header
var leftSize = size - 2 * BLOCK_OVERHEAD;
var left = changetype<Block>(start);
left.mmInfo = leftSize | FREE | (tailInfo & LEFTFREE);
left.prev = null;
left.next = null;
// tail is a zero-length used block
tail = changetype<Block>(start + size - BLOCK_OVERHEAD);
tail.mmInfo = 0 | LEFTFREE;
SETTAIL(root, tail);
insertBlock(root, left); // also merges with free left before tail / sets 'back'
return true;
}
/** Grows memory to fit at least another block of the specified size. */
function growMemory(root: Root, size: usize): void {
var pagesBefore = memory.size();
var pagesNeeded = <i32>(((size + 0xffff) & ~0xffff) >>> 16);
var pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) unreachable();
}
var pagesAfter = memory.size();
addMemory(root, <usize>pagesBefore << 16, <usize>pagesAfter << 16);
}
/** Prepares and checks an allocation size. */
function prepareSize(size: usize): usize {
if (size >= BLOCK_MAXSIZE) throw new Error("allocation too large");
return max<usize>((size + AL_MASK) & ~AL_MASK, BLOCK_MINSIZE); // align and ensure min size
}
/** Initilizes the root structure. */
export function initializeRoot(): void {
var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
var pagesBefore = memory.size();
var pagesNeeded = <i32>((((rootOffset + ROOT_SIZE) + 0xffff) & ~0xffff) >>> 16);
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
var root = changetype<Root>(rootOffset);
root.flMap = 0;
SETTAIL(root, changetype<Block>(0));
for (let fl: usize = 0; fl < FL_BITS; ++fl) {
SETSL(root, fl, 0);
for (let sl: u32 = 0; sl < SL_SIZE; ++sl) {
SETHEAD(root, fl, sl, null);
}
}
addMemory(root, (rootOffset + ROOT_SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16);
ROOT = root;
}
/** Allocates a block of the specified size. */
export function allocateBlock(root: Root, size: usize): Block {
var payloadSize = prepareSize(size);
var block = searchBlock(root, payloadSize);
if (!block) {
growMemory(root, payloadSize);
block = <Block>searchBlock(root, payloadSize);
if (DEBUG) assert(block); // must be found now
}
if (DEBUG) assert((block.mmInfo & ~TAGS_MASK) >= payloadSize); // must fit
block.gcInfo = 0;
block.rtId = 0; // not determined yet
block.rtSize = size;
removeBlock(root, <Block>block);
prepareBlock(root, <Block>block, payloadSize);
return <Block>block;
}
/** Reallocates a block to the specified size. */
export function reallocateBlock(root: Root, block: Block, size: usize): Block {
var payloadSize = prepareSize(size);
var blockInfo = block.mmInfo;
if (DEBUG) assert(!(blockInfo & FREE)); // must be used
// possibly split and update runtime size if it still fits
if (payloadSize <= (blockInfo & ~TAGS_MASK)) {
prepareBlock(root, block, payloadSize);
block.rtSize = size;
return block;
}
// merge with right free block if merger is large enough
var right = GETRIGHT(block);
var rightInfo = right.mmInfo;
if (rightInfo & FREE) {
let mergeSize = (blockInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (rightInfo & ~TAGS_MASK);
if (mergeSize >= payloadSize) {
removeBlock(root, right);
// TODO: this can yield an intermediate block larger than BLOCK_MAXSIZE, which
// is immediately split though. does this trigger any assertions / issues?
block.mmInfo = (blockInfo & TAGS_MASK) | mergeSize;
block.rtSize = size;
prepareBlock(root, block, payloadSize);
return block;
}
}
// otherwise move the block
var newBlock = allocateBlock(root, size);
newBlock.gcInfo = block.gcInfo;
newBlock.rtId = block.rtId;
memory.copy(changetype<usize>(newBlock) + BLOCK_OVERHEAD, changetype<usize>(block) + BLOCK_OVERHEAD, size);
block.mmInfo = blockInfo | FREE;
insertBlock(root, block);
return newBlock;
}
/** Frees a block. */
export function freeBlock(root: Root, block: Block): void {
var blockInfo = block.mmInfo;
assert(!(blockInfo & FREE)); // must be used (user might call through to this)
block.mmInfo = blockInfo | FREE;
insertBlock(root, block);
}

View File

@ -1,4 +1,4 @@
import "../../../runtime/assembly/index";
import "rt";
import { memory as builtin_memory } from "memory";
export namespace memory {

View File

@ -1,22 +1,21 @@
(module
(type $FUNCSIG$ii (func (param i32) (result i32)))
(type $FUNCSIG$i (func (result i32)))
(type $FUNCSIG$v (func))
(type $FUNCSIG$vii (func (param i32 i32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
(type $FUNCSIG$viii (func (param i32 i32 i32)))
(type $FUNCSIG$vi (func (param i32)))
(type $FUNCSIG$v (func))
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
(memory $0 1)
(data (i32.const 8) "\10\00\00\00>")
(data (i32.const 24) ".\00.\00/\00.\00.\00/\00r\00u\00n\00t\00i\00m\00e\00/\00a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s")
(global $../../runtime/assembly/index/ROOT (mut i32) (i32.const 0))
(data (i32.const 8) "\10\00\00\00\1e")
(data (i32.const 24) "~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s")
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
(export "memory" (memory $0))
(export "memory.allocate" (func $assembly/index/memory.allocate))
(export "memory.free" (func $assembly/index/memory.free))
(export "memory.fill" (func $assembly/index/memory.fill))
(func $../../runtime/assembly/index/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -132,7 +131,7 @@
end
end
)
(func $../../runtime/assembly/index/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -171,7 +170,7 @@
if
local.get $0
local.get $4
call $../../runtime/assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $1
local.get $2
i32.const 3
@ -218,7 +217,7 @@
if
local.get $0
local.get $3
call $../../runtime/assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $3
local.get $6
i32.const 3
@ -330,7 +329,7 @@
i32.or
i32.store offset=4
)
(func $../../runtime/assembly/index/addMemory (; 3 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(func $~lib/rt/tlsf/addMemory (; 3 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
local.get $2
block (result i32)
@ -392,14 +391,14 @@
i32.store offset=1568
local.get $0
local.get $1
call $../../runtime/assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
)
(func $../../runtime/assembly/index/initialize (; 4 ;) (type $FUNCSIG$i) (result i32)
(func $~lib/rt/tlsf/initializeRoot (; 4 ;) (type $FUNCSIG$v)
(local $0 i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
i32.const 96
i32.const 64
local.tee $3
i32.const 67107
i32.add
@ -486,10 +485,11 @@
current_memory
i32.const 16
i32.shl
call $../../runtime/assembly/index/addMemory
call $~lib/rt/tlsf/addMemory
local.get $0
global.set $~lib/rt/tlsf/ROOT
)
(func $../../runtime/assembly/index/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(func $~lib/rt/tlsf/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
local.get $0
i32.const 1073741824
@ -497,7 +497,7 @@
if
i32.const 0
i32.const 24
i32.const 466
i32.const 436
i32.const 29
call $~lib/builtins/abort
unreachable
@ -515,7 +515,7 @@
i32.gt_u
select
)
(func $../../runtime/assembly/index/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(func $~lib/rt/tlsf/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
local.get $0
local.get $1
@ -616,7 +616,7 @@
end
end
)
(func $../../runtime/assembly/index/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -655,9 +655,9 @@
current_memory
i32.const 16
i32.shl
call $../../runtime/assembly/index/addMemory
call $~lib/rt/tlsf/addMemory
)
(func $../../runtime/assembly/index/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(func $~lib/rt/tlsf/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
(local $4 i32)
local.get $1
@ -692,7 +692,7 @@
i32.store
local.get $0
local.get $1
call $../../runtime/assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
else
local.get $1
local.get $3
@ -721,23 +721,23 @@
i32.store
end
)
(func $../../runtime/assembly/index/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(func $~lib/rt/tlsf/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local $3 i32)
local.get $0
local.get $1
call $../../runtime/assembly/index/prepareSize
call $~lib/rt/tlsf/prepareSize
local.tee $3
call $../../runtime/assembly/index/searchBlock
call $~lib/rt/tlsf/searchBlock
local.tee $2
i32.eqz
if
local.get $0
local.get $3
call $../../runtime/assembly/index/growMemory
call $~lib/rt/tlsf/growMemory
local.get $0
local.get $3
call $../../runtime/assembly/index/searchBlock
call $~lib/rt/tlsf/searchBlock
local.set $2
end
local.get $2
@ -751,26 +751,25 @@
i32.store offset=12
local.get $0
local.get $2
call $../../runtime/assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $0
local.get $2
local.get $3
call $../../runtime/assembly/index/prepareBlock
call $~lib/rt/tlsf/prepareBlock
local.get $2
)
(func $assembly/index/memory.allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
global.get $../../runtime/assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.tee $1
i32.eqz
if
call $../../runtime/assembly/index/initialize
local.tee $1
global.set $../../runtime/assembly/index/ROOT
end
if (result i32)
local.get $1
else
call $~lib/rt/tlsf/initializeRoot
global.get $~lib/rt/tlsf/ROOT
end
local.get $0
call $../../runtime/assembly/index/allocateBlock
call $~lib/rt/tlsf/allocateBlock
i32.const 16
i32.add
)
@ -784,9 +783,9 @@
i32.const 1
i32.or
i32.store
global.get $../../runtime/assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.get $0
call $../../runtime/assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
)
(func $~lib/memory/memory.fill (; 12 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i64)

View File

@ -1,26 +1,26 @@
(module
(type $FUNCSIG$ii (func (param i32) (result i32)))
(type $FUNCSIG$i (func (result i32)))
(type $FUNCSIG$v (func))
(type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32)))
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
(type $FUNCSIG$vii (func (param i32 i32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$viii (func (param i32 i32 i32)))
(type $FUNCSIG$vi (func (param i32)))
(type $FUNCSIG$v (func))
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
(memory $0 1)
(data (i32.const 8) "\10\00\00\00>\00\00\00\00\00\00\00\00\00\00\00.\00.\00/\00.\00.\00/\00r\00u\00n\00t\00i\00m\00e\00/\00a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s\00")
(data (i32.const 8) "\10\00\00\00\1e\00\00\00\00\00\00\00\00\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s\00")
(data (i32.const 56) "\10\00\00\00 \00\00\00\00\00\00\00\00\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00n\00d\00e\00x\00.\00t\00s\00")
(table $0 1 funcref)
(elem (i32.const 0) $null)
(global $../../runtime/assembly/index/ROOT (mut i32) (i32.const 0))
(global $../../runtime/assembly/index/ACYCLIC_FLAG i32 (i32.const 0))
(global $~lib/memory/HEAP_BASE i32 (i32.const 88))
(global $~lib/rt/pure/ACYCLIC_FLAG i32 (i32.const 0))
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
(global $~lib/memory/HEAP_BASE i32 (i32.const 104))
(export "memory" (memory $0))
(export "memory.allocate" (func $assembly/index/memory.allocate))
(export "memory.free" (func $assembly/index/memory.free))
(export "memory.fill" (func $assembly/index/memory.fill))
(func $../../runtime/assembly/index/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -41,7 +41,7 @@
if
i32.const 0
i32.const 24
i32.const 276
i32.const 265
i32.const 13
call $~lib/builtins/abort
unreachable
@ -66,7 +66,7 @@
if
i32.const 0
i32.const 24
i32.const 278
i32.const 267
i32.const 13
call $~lib/builtins/abort
unreachable
@ -118,7 +118,7 @@
if
i32.const 0
i32.const 24
i32.const 291
i32.const 280
i32.const 13
call $~lib/builtins/abort
unreachable
@ -142,7 +142,7 @@
i32.store offset=16
end
local.get $1
block $../../runtime/assembly/index/GETHEAD|inlined.1 (result i32)
block $~lib/rt/tlsf/GETHEAD|inlined.1 (result i32)
local.get $0
local.set $10
local.get $4
@ -162,7 +162,7 @@
end
i32.eq
if
block $../../runtime/assembly/index/SETHEAD|inlined.1
block $~lib/rt/tlsf/SETHEAD|inlined.1
local.get $0
local.set $11
local.get $4
@ -186,7 +186,7 @@
local.get $7
i32.eqz
if
block $../../runtime/assembly/index/GETSL|inlined.0 (result i32)
block $~lib/rt/tlsf/GETSL|inlined.0 (result i32)
local.get $0
local.set $9
local.get $4
@ -199,7 +199,7 @@
i32.load offset=4
end
local.set $8
block $../../runtime/assembly/index/SETSL|inlined.1
block $~lib/rt/tlsf/SETSL|inlined.1
local.get $0
local.set $11
local.get $4
@ -238,7 +238,7 @@
end
end
)
(func $../../runtime/assembly/index/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -256,7 +256,7 @@
if
i32.const 0
i32.const 24
i32.const 204
i32.const 193
i32.const 13
call $~lib/builtins/abort
unreachable
@ -271,12 +271,12 @@
if
i32.const 0
i32.const 24
i32.const 206
i32.const 195
i32.const 13
call $~lib/builtins/abort
unreachable
end
block $../../runtime/assembly/index/GETRIGHT|inlined.0 (result i32)
block $~lib/rt/tlsf/GETRIGHT|inlined.0 (result i32)
local.get $1
local.set $3
local.get $3
@ -318,7 +318,7 @@
if
local.get $0
local.get $4
call $../../runtime/assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $1
local.get $2
i32.const 3
@ -327,7 +327,7 @@
i32.or
local.tee $2
i32.store
block $../../runtime/assembly/index/GETRIGHT|inlined.1 (result i32)
block $~lib/rt/tlsf/GETRIGHT|inlined.1 (result i32)
local.get $1
local.set $6
local.get $6
@ -351,7 +351,7 @@
i32.const 2
i32.and
if
block $../../runtime/assembly/index/GETFREELEFT|inlined.0 (result i32)
block $~lib/rt/tlsf/GETFREELEFT|inlined.0 (result i32)
local.get $1
local.set $3
local.get $3
@ -370,7 +370,7 @@
if
i32.const 0
i32.const 24
i32.const 227
i32.const 216
i32.const 15
call $~lib/builtins/abort
unreachable
@ -395,7 +395,7 @@
if
local.get $0
local.get $3
call $../../runtime/assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $3
local.get $6
i32.const 3
@ -433,7 +433,7 @@
if
i32.const 0
i32.const 24
i32.const 242
i32.const 231
i32.const 13
call $~lib/builtins/abort
unreachable
@ -449,7 +449,7 @@
if
i32.const 0
i32.const 24
i32.const 243
i32.const 232
i32.const 13
call $~lib/builtins/abort
unreachable
@ -506,12 +506,12 @@
if
i32.const 0
i32.const 24
i32.const 259
i32.const 248
i32.const 13
call $~lib/builtins/abort
unreachable
end
block $../../runtime/assembly/index/GETHEAD|inlined.2 (result i32)
block $~lib/rt/tlsf/GETHEAD|inlined.2 (result i32)
local.get $0
local.set $3
local.get $9
@ -542,7 +542,7 @@
local.get $1
i32.store offset=16
end
block $../../runtime/assembly/index/SETHEAD|inlined.2
block $~lib/rt/tlsf/SETHEAD|inlined.2
local.get $0
local.set $12
local.get $9
@ -571,8 +571,8 @@
i32.shl
i32.or
i32.store
block $../../runtime/assembly/index/SETSL|inlined.2
block $../../runtime/assembly/index/GETSL|inlined.1 (result i32)
block $~lib/rt/tlsf/SETSL|inlined.2
block $~lib/rt/tlsf/GETSL|inlined.1 (result i32)
local.get $0
local.set $13
local.get $9
@ -598,7 +598,7 @@
i32.store offset=4
end
)
(func $../../runtime/assembly/index/addMemory (; 3 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(func $~lib/rt/tlsf/addMemory (; 3 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
@ -629,12 +629,12 @@
if
i32.const 0
i32.const 24
i32.const 385
i32.const 374
i32.const 4
call $~lib/builtins/abort
unreachable
end
block $../../runtime/assembly/index/GETTAIL|inlined.0 (result i32)
block $~lib/rt/tlsf/GETTAIL|inlined.0 (result i32)
local.get $0
local.set $3
local.get $3
@ -654,7 +654,7 @@
if
i32.const 0
i32.const 24
i32.const 395
i32.const 384
i32.const 15
call $~lib/builtins/abort
unreachable
@ -685,7 +685,7 @@
if
i32.const 0
i32.const 24
i32.const 407
i32.const 396
i32.const 4
call $~lib/builtins/abort
unreachable
@ -740,7 +740,7 @@
i32.const 2
i32.or
i32.store
block $../../runtime/assembly/index/SETTAIL|inlined.1
block $~lib/rt/tlsf/SETTAIL|inlined.1
local.get $0
local.set $9
local.get $4
@ -751,10 +751,10 @@
end
local.get $0
local.get $8
call $../../runtime/assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
i32.const 1
)
(func $../../runtime/assembly/index/initialize (; 4 ;) (type $FUNCSIG$i) (result i32)
(func $~lib/rt/tlsf/initializeRoot (; 4 ;) (type $FUNCSIG$v)
(local $0 i32)
(local $1 i32)
(local $2 i32)
@ -808,7 +808,7 @@
local.get $3
i32.const 0
i32.store
block $../../runtime/assembly/index/SETTAIL|inlined.0
block $~lib/rt/tlsf/SETTAIL|inlined.0
local.get $3
local.set $5
i32.const 0
@ -827,7 +827,7 @@
i32.eqz
br_if $break|0
block
block $../../runtime/assembly/index/SETSL|inlined.0
block $~lib/rt/tlsf/SETSL|inlined.0
local.get $3
local.set $7
local.get $4
@ -851,7 +851,7 @@
i32.lt_u
i32.eqz
br_if $break|1
block $../../runtime/assembly/index/SETHEAD|inlined.0
block $~lib/rt/tlsf/SETHEAD|inlined.0
local.get $3
local.set $9
local.get $4
@ -904,11 +904,12 @@
current_memory
i32.const 16
i32.shl
call $../../runtime/assembly/index/addMemory
call $~lib/rt/tlsf/addMemory
drop
local.get $3
global.set $~lib/rt/tlsf/ROOT
)
(func $../../runtime/assembly/index/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(func $~lib/rt/tlsf/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
(local $2 i32)
local.get $0
@ -917,7 +918,7 @@
if
i32.const 0
i32.const 24
i32.const 466
i32.const 436
i32.const 29
call $~lib/builtins/abort
unreachable
@ -937,7 +938,7 @@
i32.gt_u
select
)
(func $../../runtime/assembly/index/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(func $~lib/rt/tlsf/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -1011,12 +1012,12 @@
if
i32.const 0
i32.const 24
i32.const 337
i32.const 326
i32.const 13
call $~lib/builtins/abort
unreachable
end
block $../../runtime/assembly/index/GETSL|inlined.2 (result i32)
block $~lib/rt/tlsf/GETSL|inlined.2 (result i32)
local.get $0
local.set $5
local.get $2
@ -1058,7 +1059,7 @@
local.get $4
i32.ctz
local.set $2
block $../../runtime/assembly/index/GETSL|inlined.3 (result i32)
block $~lib/rt/tlsf/GETSL|inlined.3 (result i32)
local.get $0
local.set $8
local.get $2
@ -1076,12 +1077,12 @@
if
i32.const 0
i32.const 24
i32.const 350
i32.const 339
i32.const 17
call $~lib/builtins/abort
unreachable
end
block $../../runtime/assembly/index/GETHEAD|inlined.3 (result i32)
block $~lib/rt/tlsf/GETHEAD|inlined.3 (result i32)
local.get $0
local.set $9
local.get $2
@ -1103,7 +1104,7 @@
local.set $7
end
else
block $../../runtime/assembly/index/GETHEAD|inlined.4 (result i32)
block $~lib/rt/tlsf/GETHEAD|inlined.4 (result i32)
local.get $0
local.set $8
local.get $2
@ -1126,7 +1127,7 @@
end
local.get $7
)
(func $../../runtime/assembly/index/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -1176,10 +1177,10 @@
local.get $7
i32.const 16
i32.shl
call $../../runtime/assembly/index/addMemory
call $~lib/rt/tlsf/addMemory
drop
)
(func $../../runtime/assembly/index/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(func $~lib/rt/tlsf/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
@ -1194,7 +1195,7 @@
if
i32.const 0
i32.const 24
i32.const 364
i32.const 353
i32.const 13
call $~lib/builtins/abort
unreachable
@ -1235,7 +1236,7 @@
i32.store
local.get $0
local.get $5
call $../../runtime/assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
else
local.get $1
local.get $3
@ -1244,7 +1245,7 @@
i32.xor
i32.and
i32.store
block $../../runtime/assembly/index/GETRIGHT|inlined.3 (result i32)
block $~lib/rt/tlsf/GETRIGHT|inlined.3 (result i32)
local.get $1
local.set $5
local.get $5
@ -1258,7 +1259,7 @@
i32.and
i32.add
end
block $../../runtime/assembly/index/GETRIGHT|inlined.2 (result i32)
block $~lib/rt/tlsf/GETRIGHT|inlined.2 (result i32)
local.get $1
local.set $5
local.get $5
@ -1280,32 +1281,32 @@
i32.store
end
)
(func $../../runtime/assembly/index/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(func $~lib/rt/tlsf/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local $3 i32)
local.get $1
call $../../runtime/assembly/index/prepareSize
call $~lib/rt/tlsf/prepareSize
local.set $2
local.get $0
local.get $2
call $../../runtime/assembly/index/searchBlock
call $~lib/rt/tlsf/searchBlock
local.set $3
local.get $3
i32.eqz
if
local.get $0
local.get $2
call $../../runtime/assembly/index/growMemory
call $~lib/rt/tlsf/growMemory
local.get $0
local.get $2
call $../../runtime/assembly/index/searchBlock
call $~lib/rt/tlsf/searchBlock
local.set $3
local.get $3
i32.eqz
if
i32.const 0
i32.const 24
i32.const 477
i32.const 466
i32.const 15
call $~lib/builtins/abort
unreachable
@ -1323,7 +1324,7 @@
if
i32.const 0
i32.const 24
i32.const 479
i32.const 468
i32.const 13
call $~lib/builtins/abort
unreachable
@ -1339,35 +1340,35 @@
i32.store offset=12
local.get $0
local.get $3
call $../../runtime/assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $0
local.get $3
local.get $2
call $../../runtime/assembly/index/prepareBlock
call $~lib/rt/tlsf/prepareBlock
local.get $3
)
(func $../../runtime/assembly/index/__mm_allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(func $~lib/rt/index/__mm_allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
global.get $../../runtime/assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.set $1
local.get $1
i32.eqz
if
call $../../runtime/assembly/index/initialize
local.tee $1
global.set $../../runtime/assembly/index/ROOT
call $~lib/rt/tlsf/initializeRoot
global.get $~lib/rt/tlsf/ROOT
local.set $1
end
local.get $1
local.get $0
call $../../runtime/assembly/index/allocateBlock
call $~lib/rt/tlsf/allocateBlock
i32.const 16
i32.add
)
(func $assembly/index/memory.allocate (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
local.get $0
call $../../runtime/assembly/index/__mm_allocate
call $~lib/rt/index/__mm_allocate
)
(func $../../runtime/assembly/index/freeBlock (; 12 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/freeBlock (; 12 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
local.get $1
i32.load
@ -1380,7 +1381,7 @@
if
i32.const 0
i32.const 24
i32.const 530
i32.const 519
i32.const 2
call $~lib/builtins/abort
unreachable
@ -1392,15 +1393,15 @@
i32.store
local.get $0
local.get $1
call $../../runtime/assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
)
(func $../../runtime/assembly/index/__mm_free (; 13 ;) (type $FUNCSIG$vi) (param $0 i32)
global.get $../../runtime/assembly/index/ROOT
(func $~lib/rt/index/__mm_free (; 13 ;) (type $FUNCSIG$vi) (param $0 i32)
global.get $~lib/rt/tlsf/ROOT
i32.eqz
if
i32.const 0
i32.const 24
i32.const 556
i32.const 72
i32.const 29
i32.const 13
call $~lib/builtins/abort
unreachable
@ -1419,21 +1420,21 @@
i32.eqz
if
i32.const 0
i32.const 24
i32.const 557
i32.const 72
i32.const 30
i32.const 2
call $~lib/builtins/abort
unreachable
end
global.get $../../runtime/assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.get $0
i32.const 16
i32.sub
call $../../runtime/assembly/index/freeBlock
call $~lib/rt/tlsf/freeBlock
)
(func $assembly/index/memory.free (; 14 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0
call $../../runtime/assembly/index/__mm_free
call $~lib/rt/index/__mm_free
)
(func $~lib/memory/memory.fill (; 15 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)

View File

@ -1,823 +1,12 @@
// An experimental standalone AssemblyScript runtime based on TLSF and PureRC.
import "rt";
// @ts-ignore: decorator
@inline const DEBUG = true;
// Alignment guarantees
// @ts-ignore: decorator
@inline const AL_BITS: u32 = 4; // 16 bytes to fit up to v128
// @ts-ignore: decorator
@inline const AL_SIZE: usize = 1 << <usize>AL_BITS;
// @ts-ignore: decorator
@inline const AL_MASK: usize = AL_SIZE - 1;
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
// see: http://www.gii.upv.es/tlsf/
// - `ffs(x)` is equivalent to `ctz(x)` with x != 0
// - `fls(x)` is equivalent to `sizeof(x) * 8 - clz(x) - 1`
// ╒══════════════ 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
// @ts-ignore: decorator
@inline const SL_BITS: u32 = 4;
// @ts-ignore: decorator
@inline const SL_SIZE: usize = 1 << <usize>SL_BITS;
// @ts-ignore: decorator
@inline const SB_BITS: usize = <usize>(SL_BITS + AL_BITS);
// @ts-ignore: decorator
@inline const SB_SIZE: usize = 1 << <usize>SB_BITS;
// @ts-ignore: decorator
@inline const FL_BITS: u32 = 31 - SB_BITS;
// [00]: < 256B (SB) [12]: < 1M
// [01]: < 512B [13]: < 2M
// [02]: < 1K [14]: < 4M
// [03]: < 2K [15]: < 8M
// [04]: < 4K [16]: < 16M
// [05]: < 8K [17]: < 32M
// [06]: < 16K [18]: < 64M
// [07]: < 32K [19]: < 128M
// [08]: < 64K [20]: < 256M
// [09]: < 128K [21]: < 512M
// [10]: < 256K [22]: <= 1G - OVERHEAD
// [11]: < 512K
// VMs limit to 2GB total (currently), making one 1G block max (or three 512M etc.) due to block overhead
// Tags stored in otherwise unused alignment bits
// @ts-ignore: decorator
@inline const FREE: usize = 1 << 0;
// @ts-ignore: decorator
@inline const LEFTFREE: usize = 1 << 1;
// @ts-ignore: decorator
@inline const TAGS_MASK: usize = FREE | LEFTFREE; // <= AL_MASK
// ╒════════════════════ Block 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┼─┤ overhead ┐
// │ size │0│L│F│ ◄─┐ info
// ├─────────────────────────────────────────────────────────┴─┴─┴─┤ │
// │ │ │
// │ ... additional runtime overhead ... │ │
// │ │ │
// ╞═══════════════════════════════════════════════════════════════╡ │ ┐ ┘
// │ if free: ◄ prev │ ◄─┤ usize
// ├───────────────────────────────────────────────────────────────┤ │
// │ if free: next ► │ ◄─┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ ... │ │ = 0
// ├───────────────────────────────────────────────────────────────┤ │
// │ if free: back ▲ │ ◄─┘
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
// F: FREE, L: LEFTFREE
@unmanaged class Block {
/** Memory manager info. */
mmInfo: usize; // WASM64 might need adaption
/** Garbage collector info. */
gcInfo: u32;
/** Runtime class id. */
rtId: u32;
/** Runtime object size. */
rtSize: u32;
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
prev: Block | null;
/** Next free block, if any. Only valid if free, otherwise part of payload. */
next: Block | null;
// If the block is free, there is a 'back'reference at its end pointing at its start.
}
// Block constants. Overhead is always present, no matter if free or used. Also, a block must have
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free.
// @ts-ignore: decorator
@inline const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back
// @ts-ignore: decorator
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
/** Gets the left block of a block. Only valid if the left block is free. */
// @ts-ignore: decorator
@inline function GETFREELEFT(block: Block): Block {
return load<Block>(changetype<usize>(block) - sizeof<usize>());
}
/** Gets the right block of of a block by advancing to the right by its size. */
// @ts-ignore: decorator
@inline function GETRIGHT(block: Block): Block {
return changetype<Block>(changetype<usize>(block) + BLOCK_OVERHEAD + (block.mmInfo & ~TAGS_MASK));
}
// ╒═════════════════════ Root 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] │ ◄─┘ │
// ╞═══════════════════════════════════════════════════════════════╡ usize
// │ head[0] │ ◄────┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ ... │ ◄────┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ head[367] │ ◄────┤
// ╞═══════════════════════════════════════════════════════════════╡ │
// │ tail │ ◄────┘
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
// S: Small blocks map
@unmanaged class Root {
/** First level bitmap. */
flMap: usize;
}
// Root constants. Where stuff is stored inside of the root structure.
// @ts-ignore: decorator
@inline const SL_START = sizeof<usize>();
// @ts-ignore: decorator
@inline const SL_END = SL_START + (FL_BITS << alignof<u32>());
// @ts-ignore: decorator
@inline const HL_START = (SL_END + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@inline const HL_END = HL_START + FL_BITS * SL_SIZE * sizeof<usize>();
// @ts-ignore: decorator
@inline const ROOT_SIZE = HL_END + sizeof<usize>();
var ROOT: Root;
/** Gets the second level map of the specified first level. */
// @ts-ignore: decorator
@inline function GETSL(root: Root, fl: usize): u32 {
return load<u32>(changetype<usize>(root) + (fl << alignof<u32>()), SL_START);
}
/** Sets the second level map of the specified first level. */
// @ts-ignore: decorator
@inline function SETSL(root: Root, fl: usize, slMap: u32): void {
store<u32>(changetype<usize>(root) + (fl << alignof<u32>()), slMap, SL_START);
}
/** Gets the head of the free list for the specified combination of first and second level. */
// @ts-ignore: decorator
@inline function GETHEAD(root: Root, fl: usize, sl: u32): Block | null {
return changetype<Block>(load<usize>(changetype<usize>(root) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>(), HL_START));
}
/** Sets the head of the free list for the specified combination of first and second level. */
// @ts-ignore: decorator
@inline function SETHEAD(root: Root, fl: usize, sl: u32, head: Block | null): void {
store<usize>(changetype<usize>(root) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>() , changetype<usize>(head), HL_START);
}
/** Gets the tail block.. */
// @ts-ignore: decorator
@inline function GETTAIL(root: Root): Block {
return load<Block>(changetype<usize>(root), HL_END);
}
/** Sets the tail block. */
// @ts-ignore: decorator
@inline function SETTAIL(root: Root, tail: Block): void {
store<Block>(changetype<usize>(root), tail, HL_END);
}
/** Inserts a previously used block back into the free list. */
function insertBlock(root: Root, block: Block): void {
if (DEBUG) assert(block); // cannot be null
var blockInfo = block.mmInfo;
if (DEBUG) assert(blockInfo & FREE); // must be free
var right = GETRIGHT(block);
var rightInfo = right.mmInfo;
// merge with right block if also free
if (rightInfo & FREE) {
let newSize = (blockInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (rightInfo & ~TAGS_MASK);
if (newSize < BLOCK_MAXSIZE) {
removeBlock(root, right);
block.mmInfo = blockInfo = (blockInfo & TAGS_MASK) | newSize;
right = GETRIGHT(block);
rightInfo = right.mmInfo;
// 'back' is set below
}
}
// merge with left block if also free
if (blockInfo & LEFTFREE) {
let left = GETFREELEFT(block);
let leftInfo = left.mmInfo;
if (DEBUG) assert(leftInfo & FREE); // must be free according to right tags
let newSize = (leftInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (blockInfo & ~TAGS_MASK);
if (newSize < BLOCK_MAXSIZE) {
removeBlock(root, left);
left.mmInfo = blockInfo = (leftInfo & TAGS_MASK) | newSize;
block = left;
// 'back' is set below
}
}
right.mmInfo = rightInfo | LEFTFREE;
// right is no longer used now, hence rightInfo is not synced
// we now know the size of the block
var size = blockInfo & ~TAGS_MASK;
if (DEBUG) assert(size >= BLOCK_MINSIZE && size < BLOCK_MAXSIZE); // must be a valid size
if (DEBUG) assert(changetype<usize>(block) + BLOCK_OVERHEAD + size == changetype<usize>(right)); // must match
// set 'back' to itself at the end of block
store<Block>(changetype<usize>(right) - sizeof<usize>(), block);
// mapping_insert
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
const inv: usize = sizeof<usize>() * 8 - 1;
fl = inv - clz<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
// perform insertion
var head = GETHEAD(root, fl, sl);
block.prev = null;
block.next = head;
if (head) head.prev = block;
SETHEAD(root, fl, sl, block);
// update first and second level maps
root.flMap |= (1 << fl);
SETSL(root, fl, GETSL(root, fl) | (1 << sl));
}
/** Removes a free block from internal lists. */
function removeBlock(root: Root, block: Block): void {
var blockInfo = block.mmInfo;
if (DEBUG) assert(blockInfo & FREE); // must be free
var size = blockInfo & ~TAGS_MASK;
if (DEBUG) assert(size >= BLOCK_MINSIZE && size < BLOCK_MAXSIZE); // must be valid
// mapping_insert
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
const inv: usize = sizeof<usize>() * 8 - 1;
fl = inv - clz<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
// 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 == GETHEAD(root, fl, sl)) {
SETHEAD(root, fl, sl, next);
// clear second level map if head is empty now
if (!next) {
let slMap = GETSL(root, fl);
SETSL(root, fl, slMap &= ~(1 << sl));
// clear first level map if second level is empty now
if (!slMap) root.flMap &= ~(1 << fl);
}
}
// note: does not alter left/back because it is likely that splitting
// is performed afterwards, invalidating those changes. so, the caller
// must perform those updates.
}
/** Searches for a free block of at least the specified size. */
function searchBlock(root: Root, size: usize): Block | null {
// size was already asserted by caller
// mapping_search
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
const halfMaxSize = BLOCK_MAXSIZE >> 1; // don't round last fl
const inv: usize = sizeof<usize>() * 8 - 1;
const invRound = inv - SL_BITS;
let requestSize = size < halfMaxSize
? size + (1 << (invRound - clz<usize>(size))) - 1
: size;
fl = inv - clz<usize>(requestSize);
sl = <u32>((requestSize >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
// search second level
var slMap = GETSL(root, fl) & (~0 << sl);
var head: Block | null;
if (!slMap) {
// search next larger first level
let flMap = root.flMap & (~0 << (fl + 1));
if (!flMap) {
head = null;
} else {
fl = ctz<usize>(flMap);
slMap = GETSL(root, fl);
if (DEBUG) assert(slMap); // can't be zero if fl points here
head = GETHEAD(root, fl, ctz<u32>(slMap));
}
} else {
head = GETHEAD(root, fl, ctz<u32>(slMap));
}
return head;
}
/** Prepares the specified block before (re-)use, possibly splitting it. */
function prepareBlock(root: Root, block: Block, size: usize): void {
// size was already asserted by caller
var blockInfo = block.mmInfo;
if (DEBUG) assert(!(size & AL_MASK)); // size must be aligned so the new block is
// split if the block can hold another MINSIZE block incl. overhead
var remaining = (blockInfo & ~TAGS_MASK) - size;
if (remaining >= BLOCK_OVERHEAD + BLOCK_MINSIZE) {
block.mmInfo = size | (blockInfo & LEFTFREE); // also discards FREE
let spare = changetype<Block>(changetype<usize>(block) + BLOCK_OVERHEAD + size);
spare.mmInfo = (remaining - BLOCK_OVERHEAD) | FREE; // not LEFTFREE
insertBlock(root, spare); // also sets 'back'
// otherwise tag block as no longer FREE and right as no longer LEFTFREE
} else {
block.mmInfo = blockInfo & ~FREE;
GETRIGHT(block).mmInfo &= ~LEFTFREE;
}
}
/** Adds more memory to the pool. */
function addMemory(root: Root, start: usize, end: usize): bool {
if (DEBUG) {
assert(
start <= end && // must be valid
!(start & AL_MASK) && // must be aligned
!(end & AL_MASK) // must be aligned
);
}
var tail = GETTAIL(root);
var tailInfo: usize = 0;
if (tail) { // more memory
if (DEBUG) assert(start >= changetype<usize>(tail) + BLOCK_OVERHEAD);
// merge with current tail if adjacent
if (start - BLOCK_OVERHEAD == changetype<usize>(tail)) {
start -= BLOCK_OVERHEAD;
tailInfo = tail.mmInfo;
} else {
// We don't do this, but a user might `memory.grow` manually
// leading to non-adjacent pages managed by TLSF.
}
} else if (DEBUG) { // first memory
assert(start >= changetype<usize>(root) + 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_OVERHEAD + BLOCK_MINSIZE + BLOCK_OVERHEAD) {
return false;
}
// left size is total minus its own and the zero-length tail's header
var leftSize = size - 2 * BLOCK_OVERHEAD;
var left = changetype<Block>(start);
left.mmInfo = leftSize | FREE | (tailInfo & LEFTFREE);
left.prev = null;
left.next = null;
// tail is a zero-length used block
tail = changetype<Block>(start + size - BLOCK_OVERHEAD);
tail.mmInfo = 0 | LEFTFREE;
SETTAIL(root, tail);
insertBlock(root, left); // also merges with free left before tail / sets 'back'
return true;
}
/** Grows memory to fit at least another block of the specified size. */
function growMemory(root: Root, size: usize): void {
var pagesBefore = memory.size();
var pagesNeeded = <i32>(((size + 0xffff) & ~0xffff) >>> 16);
var pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) unreachable();
}
var pagesAfter = memory.size();
addMemory(root, <usize>pagesBefore << 16, <usize>pagesAfter << 16);
}
/** Initilizes the root structure. */
function initialize(): Root {
var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
var pagesBefore = memory.size();
var pagesNeeded = <i32>((((rootOffset + ROOT_SIZE) + 0xffff) & ~0xffff) >>> 16);
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
var root = changetype<Root>(rootOffset);
root.flMap = 0;
SETTAIL(root, changetype<Block>(0));
for (let fl: usize = 0; fl < FL_BITS; ++fl) {
SETSL(root, fl, 0);
for (let sl: u32 = 0; sl < SL_SIZE; ++sl) {
SETHEAD(root, fl, sl, null);
}
}
addMemory(root, (rootOffset + ROOT_SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16);
return root;
}
/** Prepares and checks an allocation size. */
function prepareSize(size: usize): usize {
if (size >= BLOCK_MAXSIZE) throw new Error("allocation too large");
return max<usize>((size + AL_MASK) & ~AL_MASK, BLOCK_MINSIZE); // align and ensure min size
}
/** Allocates a block of the specified size. */
function allocateBlock(root: Root, size: usize): Block {
var payloadSize = prepareSize(size);
var block = searchBlock(root, payloadSize);
if (!block) {
growMemory(root, payloadSize);
block = <Block>searchBlock(root, payloadSize);
if (DEBUG) assert(block); // must be found now
}
if (DEBUG) assert((block.mmInfo & ~TAGS_MASK) >= payloadSize); // must fit
block.gcInfo = 0;
block.rtId = 0; // not determined yet
block.rtSize = size;
removeBlock(root, <Block>block);
prepareBlock(root, <Block>block, payloadSize);
return <Block>block;
}
/** Reallocates a block to the specified size. */
function reallocateBlock(root: Root, block: Block, size: usize): Block {
var payloadSize = prepareSize(size);
var blockInfo = block.mmInfo;
if (DEBUG) assert(!(blockInfo & FREE)); // must be used
// possibly split and update runtime size if it still fits
if (payloadSize <= (blockInfo & ~TAGS_MASK)) {
prepareBlock(root, block, payloadSize);
block.rtSize = size;
return block;
}
// merge with right free block if merger is large enough
var right = GETRIGHT(block);
var rightInfo = right.mmInfo;
if (rightInfo & FREE) {
let mergeSize = (blockInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (rightInfo & ~TAGS_MASK);
if (mergeSize >= payloadSize) {
removeBlock(root, right);
// TODO: this can yield an intermediate block larger than BLOCK_MAXSIZE, which
// is immediately split though. does this trigger any assertions / issues?
block.mmInfo = (blockInfo & TAGS_MASK) | mergeSize;
block.rtSize = size;
prepareBlock(root, block, payloadSize);
return block;
}
}
// otherwise move the block
var newBlock = allocateBlock(root, size);
newBlock.gcInfo = block.gcInfo;
newBlock.rtId = block.rtId;
memory.copy(changetype<usize>(newBlock) + BLOCK_OVERHEAD, changetype<usize>(block) + BLOCK_OVERHEAD, size);
block.mmInfo = blockInfo | FREE;
insertBlock(root, block);
return newBlock;
}
/** Frees a block. */
function freeBlock(root: Root, block: Block): void {
var blockInfo = block.mmInfo;
assert(!(blockInfo & FREE)); // must be used (user might call through to this)
block.mmInfo = blockInfo | FREE;
insertBlock(root, block);
}
// Memory manager interface.
// @ts-ignore: decorator
@global @unsafe
function __mm_allocate(size: usize): usize {
var root = ROOT;
if (!root) ROOT = root = initialize();
return changetype<usize>(allocateBlock(root, size)) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
function __mm_reallocate(data: usize, size: usize): usize {
if (DEBUG) assert(ROOT); // must be initialized
assert(data != 0 && !(data & AL_MASK)); // must exist and be aligned
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(data - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
function __mm_free(data: usize): void {
if (DEBUG) assert(ROOT); // must be initialized
assert(data != 0 && !(data & AL_MASK)); // must exist and be aligned
freeBlock(ROOT, changetype<Block>(data - BLOCK_OVERHEAD));
}
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
// TODO: make visitors eat cookies so we can compile direct calls into a switch
function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); }
function __rt_flags(classId: u32): u32 { return unreachable(); }
const ACYCLIC_FLAG: u32 = 0;
// ╒══════════════════════ GC Info structure ══════════════════════╕
// │ 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│
// ├─┼─┴─┴─┼─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
// │B│color│ refCount │
// └─┴─────┴───────────────────────────────────────────────────────┘
// B: buffered
// @ts-ignore: decorator
@inline const BUFFERED_MASK: u32 = 1 << (sizeof<u32>() * 8 - 1);
// @ts-ignore: decorator
@inline const COLOR_BITS = 3;
// @ts-ignore: decorator
@inline const COLOR_SHIFT: u32 = ctz(BUFFERED_MASK) - COLOR_BITS;
// @ts-ignore: decorator
@inline const COLOR_MASK: u32 = ((1 << COLOR_BITS) - 1) << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const REFCOUNT_MASK: u32 = (1 << COLOR_SHIFT) - 1;
// ╒════════╤═══════════════════ Colors ═══════════════════════════╕
// │ Color │ Meaning │
// ├────────┼──────────────────────────────────────────────────────┤
// │ BLACK │ In use or free │
// │ GRAY │ Possible member of cycle │
// │ WHITE │ Member of garbage cycle │
// │ PURPLE │ Possible root of cycle │
// │ RED │ Candidate cycle undergoing Σ-computation *concurrent │
// │ ORANGE │ Candidate cycle awaiting epoch boundary *concurrent │
// └────────┴──────────────────────────────────────────────────────┘
// Acyclic detection has been decoupled, hence no GREEN.
// @ts-ignore: decorator
@inline const COLOR_BLACK: u32 = 0 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_GRAY: u32 = 1 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_WHITE: u32 = 2 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_PURPLE: u32 = 3 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_RED: u32 = 4 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const COLOR_ORANGE: u32 = 5 << COLOR_SHIFT;
// @ts-ignore: decorator
@inline const VISIT_DECREMENT = 1; // guard 0
// @ts-ignore: decorator
@inline const VISIT_MARKGRAY = 2;
// @ts-ignore: decorator
@inline const VISIT_SCAN = 3;
// @ts-ignore: decorator
@inline const VISIT_SCANBLACK = 4;
// @ts-ignore: decorator
@inline const VISIT_COLLECTWHITE = 5;
// @ts-ignore: decorator
@global
function __rt_visit(s: Block, cookie: i32): void {
switch (cookie) {
case VISIT_DECREMENT: {
decrement(s);
break;
}
case VISIT_MARKGRAY: {
if (DEBUG) assert((s.gcInfo & REFCOUNT_MASK) > 0);
s.gcInfo = s.gcInfo - 1;
markGray(s);
break;
}
case VISIT_SCAN: {
scan(s);
break;
}
case VISIT_SCANBLACK: {
let info = s.gcInfo;
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
s.gcInfo = info + 1;
if ((info & COLOR_MASK) != COLOR_BLACK) {
scanBlack(s);
}
break;
}
case VISIT_COLLECTWHITE: {
collectWhite(s);
break;
}
default: if (DEBUG) assert(false);
}
}
/** Increments the reference count of the specified block by one.*/
function increment(s: Block): void {
var info = s.gcInfo;
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
s.gcInfo = info + 1;
}
/** Decrements the reference count of the specified block by one, possibly freeing it. */
function decrement(s: Block): void {
var info = s.gcInfo;
var rc = info & REFCOUNT_MASK;
if (rc == 1) {
__rt_visit_members(s, VISIT_DECREMENT);
if (!(info & BUFFERED_MASK)) {
freeBlock(ROOT, s);
} else {
s.gcInfo = BUFFERED_MASK | COLOR_BLACK | 0;
}
} else {
if (DEBUG) assert(rc > 0);
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
if (!(info & BUFFERED_MASK)) {
appendRoot(s);
}
} else {
s.gcInfo = (info & ~REFCOUNT_MASK) | (rc - 1);
}
}
}
/** Buffer of possible roots. */
// @ts-ignore: decorator
@lazy var ROOTS: usize;
/** Current absolute offset into the `ROOTS` buffer. */
// @ts-ignore: decorator
@lazy var CUR: usize = 0;
/** Current absolute end offset into the `ROOTS` buffer. */
// @ts-ignore: decorator
@lazy var END: usize = 0;
/** Appends a block to possible roots. */
function appendRoot(s: Block): void {
var cur = CUR;
if (cur >= END) {
growRoots(); // TBD: either that or pick a default and force collection on overflow
cur = CUR;
}
store<Block>(cur, s);
CUR = cur + 1;
}
/** Grows the roots buffer if it ran full. */
function growRoots(): void {
var oldRoots = ROOTS;
var oldSize = CUR - oldRoots;
var newSize = max(oldSize * 2, 64 << alignof<usize>());
var newRoots = memory.allocate(newSize);
memory.copy(newRoots, oldRoots, oldSize);
ROOTS = newRoots;
CUR = newRoots + oldSize;
END = newRoots + newSize;
}
/** Collects cyclic garbage. */
function collectCycles(): void {
// markRoots
var roots = ROOTS;
var cur = roots;
for (let pos = cur, end = CUR; pos < end; pos += sizeof<usize>()) {
let s = load<Block>(pos);
let info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_PURPLE && (info & REFCOUNT_MASK) > 0) {
markGray(s);
store<Block>(cur, s);
cur += sizeof<usize>();
} else {
if ((info & COLOR_MASK) == COLOR_BLACK && !(info & REFCOUNT_MASK)) {
freeBlock(ROOT, s);
} else {
s.gcInfo = info & ~BUFFERED_MASK;
}
}
}
CUR = cur;
// scanRoots
for (let pos = roots; pos < cur; pos += sizeof<usize>()) {
scan(load<Block>(pos));
}
// collectRoots
for (let pos = roots; pos < cur; pos += sizeof<usize>()) {
let s = load<Block>(pos);
s.gcInfo = s.gcInfo & ~BUFFERED_MASK;
collectWhite(s);
}
CUR = roots;
}
/** Marks a block as gray (possible member of cycle) during the collection phase. */
function markGray(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) != COLOR_GRAY) {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
__rt_visit_members(s, VISIT_MARKGRAY);
}
}
/** Scans a block during the collection phase, determining whether it is garbage or not. */
function scan(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_GRAY) {
if ((info & REFCOUNT_MASK) > 0) {
scanBlack(s);
} else {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
__rt_visit_members(s, VISIT_SCAN);
}
}
}
/** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
function scanBlack(s: Block): void {
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_SCANBLACK);
}
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
function collectWhite(s: Block): void {
var info = s.gcInfo;
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
__rt_visit_members(s, VISIT_COLLECTWHITE);
}
freeBlock(ROOT, s);
}
// Garbage collector interface
// @ts-ignore: decorator
@global @unsafe
function __gc_retain(ref: usize): void {
if (ref) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
function __gc_release(ref: usize): void {
if (ref) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// keep alive, everything else is reached from here
export {
__mm_allocate,
__mm_reallocate,
__mm_free,
__rt_visit,
__gc_retain,
__gc_release,
collectCycles as __gc_collect
__gc_collect
};
// @start export function main(): void {}
@start export function main(): void {}

View File

@ -133,6 +133,7 @@ function allocate(size) {
el.appendChild(er);
var ef = document.createElement("button");
ef.innerText = "free";
ef.className = "free";
el.appendChild(ef);
ef.onclick = function() {
exports.__mm_free(ptr);
@ -154,12 +155,16 @@ button:hover { background: #bbb; }
.clear { clear: both; }
/* Lists */
.fl, .sl, .hl, .seg { float: left; padding: 0.4em; margin: 0.2em; border: 1px solid #ddd; border-radius: 3px; }
.fl { min-width: 1.3em; text-align: center; }
.sl { min-width: 12em; }
.hl { min-width: 7em; font-size: 0.8em; }
.num { color: #fff; background: rgba(0, 0, 0, 0.3); padding: 0.4em; margin-left: -0.4em; border-radius: 0.2em; }
.set { background: #7f7; }
.seg { border-top: 0.3em solid #333; }
.seg button { margin-left: 0.5em; }
.sub { vertical-align: sub; font-size: 0.8em; }
.free { background: #f77; border-color: #c33; }
.free:hover { background: #c33; color: #fff; }
</style>
<h1>AssemblyScript Runtime Visualizer / TLSF</h1>

View File

@ -1,31 +1,39 @@
(module
(type $FUNCSIG$v (func))
(type $FUNCSIG$ii (func (param i32) (result i32)))
(type $FUNCSIG$i (func (result i32)))
(type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32)))
(type $FUNCSIG$vii (func (param i32 i32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
(type $FUNCSIG$viii (func (param i32 i32 i32)))
(type $FUNCSIG$vi (func (param i32)))
(type $FUNCSIG$v (func))
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
(memory $0 1)
(data (i32.const 8) "\10\00\00\00\"")
(data (i32.const 24) "a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s")
(data (i32.const 64) "\10\00\00\00\1c")
(data (i32.const 80) "~\00l\00i\00b\00/\00m\00e\00m\00o\00r\00y\00.\00t\00s")
(global $assembly/index/ROOT (mut i32) (i32.const 0))
(global $assembly/index/CUR (mut i32) (i32.const 0))
(global $assembly/index/ROOTS (mut i32) (i32.const 0))
(data (i32.const 8) "\10\00\00\00\1e")
(data (i32.const 24) "~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s")
(data (i32.const 56) "\10\00\00\00\1c")
(data (i32.const 72) "~\00l\00i\00b\00/\00m\00e\00m\00o\00r\00y\00.\00t\00s")
(global $~lib/started (mut i32) (i32.const 0))
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
(global $~lib/rt/pure/CUR (mut i32) (i32.const 0))
(global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0))
(export "memory" (memory $0))
(export "__mm_allocate" (func $assembly/index/__mm_allocate))
(export "__mm_reallocate" (func $assembly/index/__mm_reallocate))
(export "__mm_free" (func $assembly/index/__mm_free))
(export "__rt_visit" (func $assembly/index/__rt_visit))
(export "__gc_retain" (func $assembly/index/__gc_retain))
(export "__gc_release" (func $assembly/index/__gc_release))
(export "__gc_collect" (func $assembly/index/collectCycles))
(func $assembly/index/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(export "main" (func $assembly/index/main))
(export "__mm_allocate" (func $~lib/rt/index/__mm_allocate))
(export "__mm_reallocate" (func $~lib/rt/index/__mm_reallocate))
(export "__mm_free" (func $~lib/rt/index/__mm_free))
(export "__gc_retain" (func $~lib/rt/index/__gc_retain))
(export "__gc_release" (func $~lib/rt/index/__gc_release))
(export "__gc_collect" (func $~lib/rt/index/__gc_collect))
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
global.get $~lib/started
i32.eqz
if
i32.const 1
global.set $~lib/started
end
)
(func $~lib/rt/tlsf/removeBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -141,7 +149,7 @@
end
end
)
(func $assembly/index/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/insertBlock (; 3 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -180,7 +188,7 @@
if
local.get $0
local.get $4
call $assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $1
local.get $2
i32.const 3
@ -227,7 +235,7 @@
if
local.get $0
local.get $3
call $assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $3
local.get $6
i32.const 3
@ -339,7 +347,7 @@
i32.or
i32.store offset=4
)
(func $assembly/index/addMemory (; 3 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(func $~lib/rt/tlsf/addMemory (; 4 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
local.get $2
block (result i32)
@ -401,9 +409,9 @@
i32.store offset=1568
local.get $0
local.get $1
call $assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
)
(func $assembly/index/initialize (; 4 ;) (type $FUNCSIG$i) (result i32)
(func $~lib/rt/tlsf/initializeRoot (; 5 ;) (type $FUNCSIG$v)
(local $0 i32)
(local $1 i32)
(local $2 i32)
@ -495,10 +503,11 @@
current_memory
i32.const 16
i32.shl
call $assembly/index/addMemory
call $~lib/rt/tlsf/addMemory
local.get $0
global.set $~lib/rt/tlsf/ROOT
)
(func $assembly/index/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(func $~lib/rt/tlsf/prepareSize (; 6 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
local.get $0
i32.const 1073741824
@ -506,7 +515,7 @@
if
i32.const 0
i32.const 24
i32.const 466
i32.const 436
i32.const 29
call $~lib/builtins/abort
unreachable
@ -524,7 +533,7 @@
i32.gt_u
select
)
(func $assembly/index/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(func $~lib/rt/tlsf/searchBlock (; 7 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
local.get $0
local.get $1
@ -625,7 +634,7 @@
end
end
)
(func $assembly/index/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/growMemory (; 8 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
@ -664,9 +673,9 @@
current_memory
i32.const 16
i32.shl
call $assembly/index/addMemory
call $~lib/rt/tlsf/addMemory
)
(func $assembly/index/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(func $~lib/rt/tlsf/prepareBlock (; 9 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
(local $4 i32)
local.get $1
@ -701,7 +710,7 @@
i32.store
local.get $0
local.get $1
call $assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
else
local.get $1
local.get $3
@ -730,23 +739,23 @@
i32.store
end
)
(func $assembly/index/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(func $~lib/rt/tlsf/allocateBlock (; 10 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
(local $2 i32)
(local $3 i32)
local.get $0
local.get $1
call $assembly/index/prepareSize
call $~lib/rt/tlsf/prepareSize
local.tee $3
call $assembly/index/searchBlock
call $~lib/rt/tlsf/searchBlock
local.tee $2
i32.eqz
if
local.get $0
local.get $3
call $assembly/index/growMemory
call $~lib/rt/tlsf/growMemory
local.get $0
local.get $3
call $assembly/index/searchBlock
call $~lib/rt/tlsf/searchBlock
local.set $2
end
local.get $2
@ -760,30 +769,29 @@
i32.store offset=12
local.get $0
local.get $2
call $assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $0
local.get $2
local.get $3
call $assembly/index/prepareBlock
call $~lib/rt/tlsf/prepareBlock
local.get $2
)
(func $assembly/index/__mm_allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(func $~lib/rt/index/__mm_allocate (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
(local $1 i32)
global.get $assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.tee $1
i32.eqz
if
call $assembly/index/initialize
local.tee $1
global.set $assembly/index/ROOT
end
if (result i32)
local.get $1
else
call $~lib/rt/tlsf/initializeRoot
global.get $~lib/rt/tlsf/ROOT
end
local.get $0
call $assembly/index/allocateBlock
call $~lib/rt/tlsf/allocateBlock
i32.const 16
i32.add
)
(func $~lib/memory/memory.copy (; 11 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(func $~lib/memory/memory.copy (; 12 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 i32)
block $~lib/util/memory/memmove|inlined.0
local.get $0
@ -957,13 +965,13 @@
end
end
)
(func $assembly/index/reallocateBlock (; 12 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(func $~lib/rt/tlsf/reallocateBlock (; 13 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
(local $6 i32)
local.get $2
call $assembly/index/prepareSize
call $~lib/rt/tlsf/prepareSize
local.tee $3
local.get $1
i32.load
@ -975,7 +983,7 @@
local.get $0
local.get $1
local.get $3
call $assembly/index/prepareBlock
call $~lib/rt/tlsf/prepareBlock
local.get $1
local.get $2
i32.store offset=12
@ -1011,7 +1019,7 @@
if
local.get $0
local.get $6
call $assembly/index/removeBlock
call $~lib/rt/tlsf/removeBlock
local.get $1
local.get $4
i32.const 3
@ -1025,14 +1033,14 @@
local.get $0
local.get $1
local.get $3
call $assembly/index/prepareBlock
call $~lib/rt/tlsf/prepareBlock
local.get $1
return
end
end
local.get $0
local.get $2
call $assembly/index/allocateBlock
call $~lib/rt/tlsf/allocateBlock
local.tee $3
local.get $1
i32.load offset=4
@ -1056,20 +1064,20 @@
i32.store
local.get $0
local.get $1
call $assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
local.get $3
)
(func $assembly/index/__mm_reallocate (; 13 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
global.get $assembly/index/ROOT
(func $~lib/rt/index/__mm_reallocate (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
global.get $~lib/rt/tlsf/ROOT
local.get $0
i32.const 16
i32.sub
local.get $1
call $assembly/index/reallocateBlock
call $~lib/rt/tlsf/reallocateBlock
i32.const 16
i32.add
)
(func $assembly/index/freeBlock (; 14 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
(func $~lib/rt/tlsf/freeBlock (; 15 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
local.get $1
local.get $1
i32.load
@ -1078,31 +1086,50 @@
i32.store
local.get $0
local.get $1
call $assembly/index/insertBlock
call $~lib/rt/tlsf/insertBlock
)
(func $assembly/index/__mm_free (; 15 ;) (type $FUNCSIG$vi) (param $0 i32)
global.get $assembly/index/ROOT
(func $~lib/rt/index/__mm_free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
global.get $~lib/rt/tlsf/ROOT
local.get $0
i32.const 16
i32.sub
call $assembly/index/freeBlock
call $~lib/rt/tlsf/freeBlock
)
(func $assembly/index/decrement (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
(func $~lib/rt/index/__gc_retain (; 17 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0
if
local.get $0
i32.const 16
i32.sub
local.tee $0
local.get $0
i32.load offset=4
i32.const 1
i32.add
i32.store offset=4
end
)
(func $~lib/rt/index/__gc_release (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0
if
local.get $0
i32.const 16
i32.sub
local.tee $0
i32.load offset=4
i32.const 268435455
i32.and
i32.const 1
i32.eq
i32.eqz
i32.ne
if
local.get $0
i32.load offset=8
drop
end
unreachable
end
)
(func $assembly/index/markGray (; 17 ;) (type $FUNCSIG$vi) (param $0 i32)
(func $~lib/rt/pure/markGray (; 19 ;) (type $FUNCSIG$vi) (param $0 i32)
(local $1 i32)
local.get $0
i32.load offset=4
@ -1122,16 +1149,7 @@
unreachable
end
)
(func $assembly/index/scanBlack (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0
local.get $0
i32.load offset=4
i32.const -1879048193
i32.and
i32.store offset=4
unreachable
)
(func $assembly/index/scan (; 19 ;) (type $FUNCSIG$vi) (param $0 i32)
(func $~lib/rt/pure/scan (; 20 ;) (type $FUNCSIG$vi) (param $0 i32)
(local $1 i32)
local.get $0
i32.load offset=4
@ -1148,7 +1166,11 @@
i32.gt_u
if
local.get $0
call $assembly/index/scanBlack
local.get $0
i32.load offset=4
i32.const -1879048193
i32.and
i32.store offset=4
else
local.get $0
local.get $1
@ -1157,11 +1179,11 @@
i32.const 536870912
i32.or
i32.store offset=4
end
unreachable
end
end
)
(func $assembly/index/collectWhite (; 20 ;) (type $FUNCSIG$vi) (param $0 i32)
(func $~lib/rt/pure/collectWhite (; 21 ;) (type $FUNCSIG$vi) (param $0 i32)
(local $1 i32)
local.get $0
i32.load offset=4
@ -1179,95 +1201,22 @@
if
unreachable
end
global.get $assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.get $0
call $assembly/index/freeBlock
call $~lib/rt/tlsf/freeBlock
)
(func $assembly/index/__rt_visit (; 21 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
block $break|0
block $case4|0
block $case3|0
block $case2|0
block $case1|0
block $case0|0
local.get $1
i32.const 1
i32.sub
br_table $case0|0 $case1|0 $case2|0 $case3|0 $case4|0 $break|0
end
local.get $0
call $assembly/index/decrement
br $break|0
end
local.get $0
local.get $0
i32.load offset=4
i32.const 1
i32.sub
i32.store offset=4
local.get $0
call $assembly/index/markGray
br $break|0
end
local.get $0
call $assembly/index/scan
br $break|0
end
local.get $0
local.get $0
i32.load offset=4
local.tee $1
i32.const 1
i32.add
i32.store offset=4
local.get $1
i32.const 1879048192
i32.and
if
local.get $0
call $assembly/index/scanBlack
end
br $break|0
end
local.get $0
call $assembly/index/collectWhite
end
)
(func $assembly/index/__gc_retain (; 22 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0
if
local.get $0
i32.const 16
i32.sub
local.tee $0
local.get $0
i32.load offset=4
i32.const 1
i32.add
i32.store offset=4
end
)
(func $assembly/index/__gc_release (; 23 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0
if
local.get $0
i32.const 16
i32.sub
call $assembly/index/decrement
end
)
(func $assembly/index/collectCycles (; 24 ;) (type $FUNCSIG$v)
(func $~lib/rt/pure/collectCycles (; 22 ;) (type $FUNCSIG$v)
(local $0 i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
global.get $assembly/index/ROOTS
global.get $~lib/rt/pure/ROOTS
local.tee $5
local.tee $2
local.set $3
global.get $assembly/index/CUR
global.get $~lib/rt/pure/CUR
local.set $0
loop $repeat|0
block $break|0
@ -1293,7 +1242,7 @@
select
if
local.get $4
call $assembly/index/markGray
call $~lib/rt/pure/markGray
local.get $2
local.get $4
i32.store
@ -1312,9 +1261,9 @@
i32.and
select
if
global.get $assembly/index/ROOT
global.get $~lib/rt/tlsf/ROOT
local.get $4
call $assembly/index/freeBlock
call $~lib/rt/tlsf/freeBlock
else
local.get $4
local.get $1
@ -1331,7 +1280,7 @@
end
end
local.get $2
global.set $assembly/index/CUR
global.set $~lib/rt/pure/CUR
local.get $5
local.set $0
loop $repeat|1
@ -1342,7 +1291,7 @@
br_if $break|1
local.get $0
i32.load
call $assembly/index/scan
call $~lib/rt/pure/scan
local.get $0
i32.const 4
i32.add
@ -1367,7 +1316,7 @@
i32.and
i32.store offset=4
local.get $1
call $assembly/index/collectWhite
call $~lib/rt/pure/collectWhite
local.get $0
i32.const 4
i32.add
@ -1376,9 +1325,12 @@
end
end
local.get $5
global.set $assembly/index/CUR
global.set $~lib/rt/pure/CUR
)
(func $null (; 25 ;) (type $FUNCSIG$v)
(func $~lib/rt/index/__gc_collect (; 23 ;) (type $FUNCSIG$v)
call $~lib/rt/pure/collectCycles
)
(func $start (; 24 ;) (type $FUNCSIG$v)
nop
)
)

Binary file not shown.

File diff suppressed because it is too large Load Diff