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; 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. */ /** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
makeInsertRef( makeInsertRef(
valueExpr: ExpressionRef, 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"; import { memory as builtin_memory } from "memory";
export namespace memory { export namespace memory {

View File

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

View File

@ -1,26 +1,26 @@
(module (module
(type $FUNCSIG$ii (func (param i32) (result i32))) (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$iiii (func (param i32 i32 i32) (result i32)))
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32))) (type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
(type $FUNCSIG$vii (func (param i32 i32))) (type $FUNCSIG$vii (func (param i32 i32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32))) (type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$viii (func (param i32 i32 i32))) (type $FUNCSIG$viii (func (param i32 i32 i32)))
(type $FUNCSIG$vi (func (param i32))) (type $FUNCSIG$vi (func (param i32)))
(type $FUNCSIG$v (func))
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
(memory $0 1) (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) (table $0 1 funcref)
(elem (i32.const 0) $null) (elem (i32.const 0) $null)
(global $../../runtime/assembly/index/ROOT (mut i32) (i32.const 0)) (global $~lib/rt/pure/ACYCLIC_FLAG i32 (i32.const 0))
(global $../../runtime/assembly/index/ACYCLIC_FLAG i32 (i32.const 0)) (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
(global $~lib/memory/HEAP_BASE i32 (i32.const 88)) (global $~lib/memory/HEAP_BASE i32 (i32.const 104))
(export "memory" (memory $0)) (export "memory" (memory $0))
(export "memory.allocate" (func $assembly/index/memory.allocate)) (export "memory.allocate" (func $assembly/index/memory.allocate))
(export "memory.free" (func $assembly/index/memory.free)) (export "memory.free" (func $assembly/index/memory.free))
(export "memory.fill" (func $assembly/index/memory.fill)) (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 $2 i32)
(local $3 i32) (local $3 i32)
(local $4 i32) (local $4 i32)
@ -41,7 +41,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 276 i32.const 265
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -66,7 +66,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 278 i32.const 267
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -118,7 +118,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 291 i32.const 280
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -142,7 +142,7 @@
i32.store offset=16 i32.store offset=16
end end
local.get $1 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.get $0
local.set $10 local.set $10
local.get $4 local.get $4
@ -162,7 +162,7 @@
end end
i32.eq i32.eq
if if
block $../../runtime/assembly/index/SETHEAD|inlined.1 block $~lib/rt/tlsf/SETHEAD|inlined.1
local.get $0 local.get $0
local.set $11 local.set $11
local.get $4 local.get $4
@ -186,7 +186,7 @@
local.get $7 local.get $7
i32.eqz i32.eqz
if if
block $../../runtime/assembly/index/GETSL|inlined.0 (result i32) block $~lib/rt/tlsf/GETSL|inlined.0 (result i32)
local.get $0 local.get $0
local.set $9 local.set $9
local.get $4 local.get $4
@ -199,7 +199,7 @@
i32.load offset=4 i32.load offset=4
end end
local.set $8 local.set $8
block $../../runtime/assembly/index/SETSL|inlined.1 block $~lib/rt/tlsf/SETSL|inlined.1
local.get $0 local.get $0
local.set $11 local.set $11
local.get $4 local.get $4
@ -238,7 +238,7 @@
end end
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 $2 i32)
(local $3 i32) (local $3 i32)
(local $4 i32) (local $4 i32)
@ -256,7 +256,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 204 i32.const 193
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -271,12 +271,12 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 206 i32.const 195
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
end end
block $../../runtime/assembly/index/GETRIGHT|inlined.0 (result i32) block $~lib/rt/tlsf/GETRIGHT|inlined.0 (result i32)
local.get $1 local.get $1
local.set $3 local.set $3
local.get $3 local.get $3
@ -318,7 +318,7 @@
if if
local.get $0 local.get $0
local.get $4 local.get $4
call $../../runtime/assembly/index/removeBlock call $~lib/rt/tlsf/removeBlock
local.get $1 local.get $1
local.get $2 local.get $2
i32.const 3 i32.const 3
@ -327,7 +327,7 @@
i32.or i32.or
local.tee $2 local.tee $2
i32.store i32.store
block $../../runtime/assembly/index/GETRIGHT|inlined.1 (result i32) block $~lib/rt/tlsf/GETRIGHT|inlined.1 (result i32)
local.get $1 local.get $1
local.set $6 local.set $6
local.get $6 local.get $6
@ -351,7 +351,7 @@
i32.const 2 i32.const 2
i32.and i32.and
if if
block $../../runtime/assembly/index/GETFREELEFT|inlined.0 (result i32) block $~lib/rt/tlsf/GETFREELEFT|inlined.0 (result i32)
local.get $1 local.get $1
local.set $3 local.set $3
local.get $3 local.get $3
@ -370,7 +370,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 227 i32.const 216
i32.const 15 i32.const 15
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -395,7 +395,7 @@
if if
local.get $0 local.get $0
local.get $3 local.get $3
call $../../runtime/assembly/index/removeBlock call $~lib/rt/tlsf/removeBlock
local.get $3 local.get $3
local.get $6 local.get $6
i32.const 3 i32.const 3
@ -433,7 +433,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 242 i32.const 231
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -449,7 +449,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 243 i32.const 232
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -506,12 +506,12 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 259 i32.const 248
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
end end
block $../../runtime/assembly/index/GETHEAD|inlined.2 (result i32) block $~lib/rt/tlsf/GETHEAD|inlined.2 (result i32)
local.get $0 local.get $0
local.set $3 local.set $3
local.get $9 local.get $9
@ -542,7 +542,7 @@
local.get $1 local.get $1
i32.store offset=16 i32.store offset=16
end end
block $../../runtime/assembly/index/SETHEAD|inlined.2 block $~lib/rt/tlsf/SETHEAD|inlined.2
local.get $0 local.get $0
local.set $12 local.set $12
local.get $9 local.get $9
@ -571,8 +571,8 @@
i32.shl i32.shl
i32.or i32.or
i32.store i32.store
block $../../runtime/assembly/index/SETSL|inlined.2 block $~lib/rt/tlsf/SETSL|inlined.2
block $../../runtime/assembly/index/GETSL|inlined.1 (result i32) block $~lib/rt/tlsf/GETSL|inlined.1 (result i32)
local.get $0 local.get $0
local.set $13 local.set $13
local.get $9 local.get $9
@ -598,7 +598,7 @@
i32.store offset=4 i32.store offset=4
end 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 $3 i32)
(local $4 i32) (local $4 i32)
(local $5 i32) (local $5 i32)
@ -629,12 +629,12 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 385 i32.const 374
i32.const 4 i32.const 4
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
end end
block $../../runtime/assembly/index/GETTAIL|inlined.0 (result i32) block $~lib/rt/tlsf/GETTAIL|inlined.0 (result i32)
local.get $0 local.get $0
local.set $3 local.set $3
local.get $3 local.get $3
@ -654,7 +654,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 395 i32.const 384
i32.const 15 i32.const 15
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -685,7 +685,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 407 i32.const 396
i32.const 4 i32.const 4
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -740,7 +740,7 @@
i32.const 2 i32.const 2
i32.or i32.or
i32.store i32.store
block $../../runtime/assembly/index/SETTAIL|inlined.1 block $~lib/rt/tlsf/SETTAIL|inlined.1
local.get $0 local.get $0
local.set $9 local.set $9
local.get $4 local.get $4
@ -751,10 +751,10 @@
end end
local.get $0 local.get $0
local.get $8 local.get $8
call $../../runtime/assembly/index/insertBlock call $~lib/rt/tlsf/insertBlock
i32.const 1 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 $0 i32)
(local $1 i32) (local $1 i32)
(local $2 i32) (local $2 i32)
@ -808,7 +808,7 @@
local.get $3 local.get $3
i32.const 0 i32.const 0
i32.store i32.store
block $../../runtime/assembly/index/SETTAIL|inlined.0 block $~lib/rt/tlsf/SETTAIL|inlined.0
local.get $3 local.get $3
local.set $5 local.set $5
i32.const 0 i32.const 0
@ -827,7 +827,7 @@
i32.eqz i32.eqz
br_if $break|0 br_if $break|0
block block
block $../../runtime/assembly/index/SETSL|inlined.0 block $~lib/rt/tlsf/SETSL|inlined.0
local.get $3 local.get $3
local.set $7 local.set $7
local.get $4 local.get $4
@ -851,7 +851,7 @@
i32.lt_u i32.lt_u
i32.eqz i32.eqz
br_if $break|1 br_if $break|1
block $../../runtime/assembly/index/SETHEAD|inlined.0 block $~lib/rt/tlsf/SETHEAD|inlined.0
local.get $3 local.get $3
local.set $9 local.set $9
local.get $4 local.get $4
@ -904,11 +904,12 @@
current_memory current_memory
i32.const 16 i32.const 16
i32.shl i32.shl
call $../../runtime/assembly/index/addMemory call $~lib/rt/tlsf/addMemory
drop drop
local.get $3 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 $1 i32)
(local $2 i32) (local $2 i32)
local.get $0 local.get $0
@ -917,7 +918,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 466 i32.const 436
i32.const 29 i32.const 29
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -937,7 +938,7 @@
i32.gt_u i32.gt_u
select 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 $2 i32)
(local $3 i32) (local $3 i32)
(local $4 i32) (local $4 i32)
@ -1011,12 +1012,12 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 337 i32.const 326
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
end end
block $../../runtime/assembly/index/GETSL|inlined.2 (result i32) block $~lib/rt/tlsf/GETSL|inlined.2 (result i32)
local.get $0 local.get $0
local.set $5 local.set $5
local.get $2 local.get $2
@ -1058,7 +1059,7 @@
local.get $4 local.get $4
i32.ctz i32.ctz
local.set $2 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.get $0
local.set $8 local.set $8
local.get $2 local.get $2
@ -1076,12 +1077,12 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 350 i32.const 339
i32.const 17 i32.const 17
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
end end
block $../../runtime/assembly/index/GETHEAD|inlined.3 (result i32) block $~lib/rt/tlsf/GETHEAD|inlined.3 (result i32)
local.get $0 local.get $0
local.set $9 local.set $9
local.get $2 local.get $2
@ -1103,7 +1104,7 @@
local.set $7 local.set $7
end end
else else
block $../../runtime/assembly/index/GETHEAD|inlined.4 (result i32) block $~lib/rt/tlsf/GETHEAD|inlined.4 (result i32)
local.get $0 local.get $0
local.set $8 local.set $8
local.get $2 local.get $2
@ -1126,7 +1127,7 @@
end end
local.get $7 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 $2 i32)
(local $3 i32) (local $3 i32)
(local $4 i32) (local $4 i32)
@ -1176,10 +1177,10 @@
local.get $7 local.get $7
i32.const 16 i32.const 16
i32.shl i32.shl
call $../../runtime/assembly/index/addMemory call $~lib/rt/tlsf/addMemory
drop 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 $3 i32)
(local $4 i32) (local $4 i32)
(local $5 i32) (local $5 i32)
@ -1194,7 +1195,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 364 i32.const 353
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1235,7 +1236,7 @@
i32.store i32.store
local.get $0 local.get $0
local.get $5 local.get $5
call $../../runtime/assembly/index/insertBlock call $~lib/rt/tlsf/insertBlock
else else
local.get $1 local.get $1
local.get $3 local.get $3
@ -1244,7 +1245,7 @@
i32.xor i32.xor
i32.and i32.and
i32.store i32.store
block $../../runtime/assembly/index/GETRIGHT|inlined.3 (result i32) block $~lib/rt/tlsf/GETRIGHT|inlined.3 (result i32)
local.get $1 local.get $1
local.set $5 local.set $5
local.get $5 local.get $5
@ -1258,7 +1259,7 @@
i32.and i32.and
i32.add i32.add
end end
block $../../runtime/assembly/index/GETRIGHT|inlined.2 (result i32) block $~lib/rt/tlsf/GETRIGHT|inlined.2 (result i32)
local.get $1 local.get $1
local.set $5 local.set $5
local.get $5 local.get $5
@ -1280,32 +1281,32 @@
i32.store i32.store
end 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 $2 i32)
(local $3 i32) (local $3 i32)
local.get $1 local.get $1
call $../../runtime/assembly/index/prepareSize call $~lib/rt/tlsf/prepareSize
local.set $2 local.set $2
local.get $0 local.get $0
local.get $2 local.get $2
call $../../runtime/assembly/index/searchBlock call $~lib/rt/tlsf/searchBlock
local.set $3 local.set $3
local.get $3 local.get $3
i32.eqz i32.eqz
if if
local.get $0 local.get $0
local.get $2 local.get $2
call $../../runtime/assembly/index/growMemory call $~lib/rt/tlsf/growMemory
local.get $0 local.get $0
local.get $2 local.get $2
call $../../runtime/assembly/index/searchBlock call $~lib/rt/tlsf/searchBlock
local.set $3 local.set $3
local.get $3 local.get $3
i32.eqz i32.eqz
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 477 i32.const 466
i32.const 15 i32.const 15
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1323,7 +1324,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 479 i32.const 468
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1339,35 +1340,35 @@
i32.store offset=12 i32.store offset=12
local.get $0 local.get $0
local.get $3 local.get $3
call $../../runtime/assembly/index/removeBlock call $~lib/rt/tlsf/removeBlock
local.get $0 local.get $0
local.get $3 local.get $3
local.get $2 local.get $2
call $../../runtime/assembly/index/prepareBlock call $~lib/rt/tlsf/prepareBlock
local.get $3 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) (local $1 i32)
global.get $../../runtime/assembly/index/ROOT global.get $~lib/rt/tlsf/ROOT
local.set $1 local.set $1
local.get $1 local.get $1
i32.eqz i32.eqz
if if
call $../../runtime/assembly/index/initialize call $~lib/rt/tlsf/initializeRoot
local.tee $1 global.get $~lib/rt/tlsf/ROOT
global.set $../../runtime/assembly/index/ROOT local.set $1
end end
local.get $1 local.get $1
local.get $0 local.get $0
call $../../runtime/assembly/index/allocateBlock call $~lib/rt/tlsf/allocateBlock
i32.const 16 i32.const 16
i32.add i32.add
) )
(func $assembly/index/memory.allocate (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) (func $assembly/index/memory.allocate (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
local.get $0 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 $2 i32)
local.get $1 local.get $1
i32.load i32.load
@ -1380,7 +1381,7 @@
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 24
i32.const 530 i32.const 519
i32.const 2 i32.const 2
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1392,15 +1393,15 @@
i32.store i32.store
local.get $0 local.get $0
local.get $1 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) (func $~lib/rt/index/__mm_free (; 13 ;) (type $FUNCSIG$vi) (param $0 i32)
global.get $../../runtime/assembly/index/ROOT global.get $~lib/rt/tlsf/ROOT
i32.eqz i32.eqz
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 72
i32.const 556 i32.const 29
i32.const 13 i32.const 13
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
@ -1419,21 +1420,21 @@
i32.eqz i32.eqz
if if
i32.const 0 i32.const 0
i32.const 24 i32.const 72
i32.const 557 i32.const 30
i32.const 2 i32.const 2
call $~lib/builtins/abort call $~lib/builtins/abort
unreachable unreachable
end end
global.get $../../runtime/assembly/index/ROOT global.get $~lib/rt/tlsf/ROOT
local.get $0 local.get $0
i32.const 16 i32.const 16
i32.sub i32.sub
call $../../runtime/assembly/index/freeBlock call $~lib/rt/tlsf/freeBlock
) )
(func $assembly/index/memory.free (; 14 ;) (type $FUNCSIG$vi) (param $0 i32) (func $assembly/index/memory.free (; 14 ;) (type $FUNCSIG$vi) (param $0 i32)
local.get $0 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) (func $~lib/memory/memory.fill (; 15 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
(local $3 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 { export {
__mm_allocate, __mm_allocate,
__mm_reallocate, __mm_reallocate,
__mm_free, __mm_free,
__rt_visit,
__gc_retain, __gc_retain,
__gc_release, __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); el.appendChild(er);
var ef = document.createElement("button"); var ef = document.createElement("button");
ef.innerText = "free"; ef.innerText = "free";
ef.className = "free";
el.appendChild(ef); el.appendChild(ef);
ef.onclick = function() { ef.onclick = function() {
exports.__mm_free(ptr); exports.__mm_free(ptr);
@ -154,12 +155,16 @@ button:hover { background: #bbb; }
.clear { clear: both; } .clear { clear: both; }
/* Lists */ /* Lists */
.fl, .sl, .hl, .seg { float: left; padding: 0.4em; margin: 0.2em; border: 1px solid #ddd; border-radius: 3px; } .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; } .sl { min-width: 12em; }
.hl { min-width: 7em; font-size: 0.8em; } .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; } .num { color: #fff; background: rgba(0, 0, 0, 0.3); padding: 0.4em; margin-left: -0.4em; border-radius: 0.2em; }
.set { background: #7f7; } .set { background: #7f7; }
.seg { border-top: 0.3em solid #333; }
.seg button { margin-left: 0.5em; } .seg button { margin-left: 0.5em; }
.sub { vertical-align: sub; font-size: 0.8em; } .sub { vertical-align: sub; font-size: 0.8em; }
.free { background: #f77; border-color: #c33; }
.free:hover { background: #c33; color: #fff; }
</style> </style>
<h1>AssemblyScript Runtime Visualizer / TLSF</h1> <h1>AssemblyScript Runtime Visualizer / TLSF</h1>

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff