mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-25 15:12:12 +00:00
refactor into stdlib
This commit is contained in:
parent
ffdda4b695
commit
8216cf3361
@ -8378,6 +8378,66 @@ export class Compiler extends DiagnosticEmitter {
|
||||
return stmts;
|
||||
}
|
||||
|
||||
/** Wraps a reference in a `retain` call. Returns the reference if `tempLocal` is specified. */
|
||||
makeRetain(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
|
||||
var module = this.module;
|
||||
var program = this.program;
|
||||
var retainFn = assert(program.retainRef);
|
||||
this.compileFunction(retainFn);
|
||||
if (tempIndex >= 0) {
|
||||
let nativeSizeType = this.options.nativeSizeType;
|
||||
return module.createBlock(null, [
|
||||
module.createCall(retainFn.internalName, [
|
||||
module.createTeeLocal(tempIndex, valueExpr)
|
||||
], NativeType.None),
|
||||
module.createGetLocal(tempIndex, nativeSizeType)
|
||||
], nativeSizeType);
|
||||
} else {
|
||||
return module.createCall(retainFn.internalName, [ valueExpr ], NativeType.None);
|
||||
}
|
||||
}
|
||||
|
||||
/** Wraps a reference in `release` call. Returns the reference if `tempLocal` is specified. */
|
||||
makeRelease(valueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
|
||||
var module = this.module;
|
||||
var program = this.program;
|
||||
var releaseFn = assert(program.releaseRef);
|
||||
this.compileFunction(releaseFn);
|
||||
if (tempIndex >= 0) {
|
||||
let nativeSizeType = this.options.nativeSizeType;
|
||||
return module.createBlock(null, [
|
||||
module.createCall(releaseFn.internalName, [
|
||||
module.createTeeLocal(tempIndex, valueExpr)
|
||||
], NativeType.None),
|
||||
module.createGetLocal(tempIndex, nativeSizeType)
|
||||
], nativeSizeType);
|
||||
} else {
|
||||
return module.createCall(releaseFn.internalName, [ valueExpr ], NativeType.None);
|
||||
}
|
||||
}
|
||||
|
||||
/** Wraps a new and an old reference in a sequence of `retain` and `release` calls. */
|
||||
makeRetainRelease(newValueExpr: ExpressionRef, oldValueExpr: ExpressionRef, tempIndex: i32 = -1): ExpressionRef {
|
||||
// TODO: checking `newValue != oldValue` significantly reduces strain on the roots buffer
|
||||
// when cyclic structures may be immediately released but also requires a tempIndex. might
|
||||
// be worth to require a temp here. furthermore it might be worth to require it for retain
|
||||
// and release as well so we can emit != null checks where necessary only?
|
||||
var module = this.module;
|
||||
if (tempIndex >= 0) {
|
||||
let nativeSizeType = this.options.nativeSizeType;
|
||||
return module.createBlock(null, [
|
||||
this.makeRetain(module.createTeeLocal(tempIndex, newValueExpr)),
|
||||
this.makeRelease(oldValueExpr),
|
||||
module.createGetLocal(tempIndex, nativeSizeType)
|
||||
], nativeSizeType);
|
||||
} else {
|
||||
return module.createBlock(null, [
|
||||
this.makeRetain(newValueExpr),
|
||||
this.makeRelease(oldValueExpr)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prepares the insertion of a reference into an _uninitialized_ parent using the GC interface. */
|
||||
makeInsertRef(
|
||||
valueExpr: ExpressionRef,
|
||||
|
6
std/assembly/rt/README.md
Normal file
6
std/assembly/rt/README.md
Normal 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
13
std/assembly/rt/common.ts
Normal 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
52
std/assembly/rt/index.ts
Normal 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
239
std/assembly/rt/pure.ts
Normal 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
522
std/assembly/rt/tlsf.ts
Normal 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);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import "../../../runtime/assembly/index";
|
||||
import "rt";
|
||||
import { memory as builtin_memory } from "memory";
|
||||
|
||||
export namespace memory {
|
||||
|
@ -1,22 +1,21 @@
|
||||
(module
|
||||
(type $FUNCSIG$ii (func (param i32) (result i32)))
|
||||
(type $FUNCSIG$i (func (result i32)))
|
||||
(type $FUNCSIG$v (func))
|
||||
(type $FUNCSIG$vii (func (param i32 i32)))
|
||||
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
|
||||
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
|
||||
(type $FUNCSIG$viii (func (param i32 i32 i32)))
|
||||
(type $FUNCSIG$vi (func (param i32)))
|
||||
(type $FUNCSIG$v (func))
|
||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
(memory $0 1)
|
||||
(data (i32.const 8) "\10\00\00\00>")
|
||||
(data (i32.const 24) ".\00.\00/\00.\00.\00/\00r\00u\00n\00t\00i\00m\00e\00/\00a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s")
|
||||
(global $../../runtime/assembly/index/ROOT (mut i32) (i32.const 0))
|
||||
(data (i32.const 8) "\10\00\00\00\1e")
|
||||
(data (i32.const 24) "~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s")
|
||||
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
|
||||
(export "memory" (memory $0))
|
||||
(export "memory.allocate" (func $assembly/index/memory.allocate))
|
||||
(export "memory.free" (func $assembly/index/memory.free))
|
||||
(export "memory.fill" (func $assembly/index/memory.fill))
|
||||
(func $../../runtime/assembly/index/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -132,7 +131,7 @@
|
||||
end
|
||||
end
|
||||
)
|
||||
(func $../../runtime/assembly/index/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -171,7 +170,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $4
|
||||
call $../../runtime/assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $1
|
||||
local.get $2
|
||||
i32.const 3
|
||||
@ -218,7 +217,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $../../runtime/assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $3
|
||||
local.get $6
|
||||
i32.const 3
|
||||
@ -330,7 +329,7 @@
|
||||
i32.or
|
||||
i32.store offset=4
|
||||
)
|
||||
(func $../../runtime/assembly/index/addMemory (; 3 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(func $~lib/rt/tlsf/addMemory (; 3 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
local.get $2
|
||||
block (result i32)
|
||||
@ -392,14 +391,14 @@
|
||||
i32.store offset=1568
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $../../runtime/assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
)
|
||||
(func $../../runtime/assembly/index/initialize (; 4 ;) (type $FUNCSIG$i) (result i32)
|
||||
(func $~lib/rt/tlsf/initializeRoot (; 4 ;) (type $FUNCSIG$v)
|
||||
(local $0 i32)
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
i32.const 96
|
||||
i32.const 64
|
||||
local.tee $3
|
||||
i32.const 67107
|
||||
i32.add
|
||||
@ -486,10 +485,11 @@
|
||||
current_memory
|
||||
i32.const 16
|
||||
i32.shl
|
||||
call $../../runtime/assembly/index/addMemory
|
||||
call $~lib/rt/tlsf/addMemory
|
||||
local.get $0
|
||||
global.set $~lib/rt/tlsf/ROOT
|
||||
)
|
||||
(func $../../runtime/assembly/index/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(local $1 i32)
|
||||
local.get $0
|
||||
i32.const 1073741824
|
||||
@ -497,7 +497,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 466
|
||||
i32.const 436
|
||||
i32.const 29
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -515,7 +515,7 @@
|
||||
i32.gt_u
|
||||
select
|
||||
)
|
||||
(func $../../runtime/assembly/index/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(local $2 i32)
|
||||
local.get $0
|
||||
local.get $1
|
||||
@ -616,7 +616,7 @@
|
||||
end
|
||||
end
|
||||
)
|
||||
(func $../../runtime/assembly/index/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -655,9 +655,9 @@
|
||||
current_memory
|
||||
i32.const 16
|
||||
i32.shl
|
||||
call $../../runtime/assembly/index/addMemory
|
||||
call $~lib/rt/tlsf/addMemory
|
||||
)
|
||||
(func $../../runtime/assembly/index/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(func $~lib/rt/tlsf/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
local.get $1
|
||||
@ -692,7 +692,7 @@
|
||||
i32.store
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $../../runtime/assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
else
|
||||
local.get $1
|
||||
local.get $3
|
||||
@ -721,23 +721,23 @@
|
||||
i32.store
|
||||
end
|
||||
)
|
||||
(func $../../runtime/assembly/index/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $../../runtime/assembly/index/prepareSize
|
||||
call $~lib/rt/tlsf/prepareSize
|
||||
local.tee $3
|
||||
call $../../runtime/assembly/index/searchBlock
|
||||
call $~lib/rt/tlsf/searchBlock
|
||||
local.tee $2
|
||||
i32.eqz
|
||||
if
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $../../runtime/assembly/index/growMemory
|
||||
call $~lib/rt/tlsf/growMemory
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $../../runtime/assembly/index/searchBlock
|
||||
call $~lib/rt/tlsf/searchBlock
|
||||
local.set $2
|
||||
end
|
||||
local.get $2
|
||||
@ -751,26 +751,25 @@
|
||||
i32.store offset=12
|
||||
local.get $0
|
||||
local.get $2
|
||||
call $../../runtime/assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $0
|
||||
local.get $2
|
||||
local.get $3
|
||||
call $../../runtime/assembly/index/prepareBlock
|
||||
call $~lib/rt/tlsf/prepareBlock
|
||||
local.get $2
|
||||
)
|
||||
(func $assembly/index/memory.allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(local $1 i32)
|
||||
global.get $../../runtime/assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.tee $1
|
||||
i32.eqz
|
||||
if
|
||||
call $../../runtime/assembly/index/initialize
|
||||
local.tee $1
|
||||
global.set $../../runtime/assembly/index/ROOT
|
||||
if (result i32)
|
||||
local.get $1
|
||||
else
|
||||
call $~lib/rt/tlsf/initializeRoot
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
end
|
||||
local.get $1
|
||||
local.get $0
|
||||
call $../../runtime/assembly/index/allocateBlock
|
||||
call $~lib/rt/tlsf/allocateBlock
|
||||
i32.const 16
|
||||
i32.add
|
||||
)
|
||||
@ -784,9 +783,9 @@
|
||||
i32.const 1
|
||||
i32.or
|
||||
i32.store
|
||||
global.get $../../runtime/assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.get $0
|
||||
call $../../runtime/assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
)
|
||||
(func $~lib/memory/memory.fill (; 12 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i64)
|
||||
|
@ -1,26 +1,26 @@
|
||||
(module
|
||||
(type $FUNCSIG$ii (func (param i32) (result i32)))
|
||||
(type $FUNCSIG$i (func (result i32)))
|
||||
(type $FUNCSIG$v (func))
|
||||
(type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32)))
|
||||
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
|
||||
(type $FUNCSIG$vii (func (param i32 i32)))
|
||||
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
|
||||
(type $FUNCSIG$viii (func (param i32 i32 i32)))
|
||||
(type $FUNCSIG$vi (func (param i32)))
|
||||
(type $FUNCSIG$v (func))
|
||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
(memory $0 1)
|
||||
(data (i32.const 8) "\10\00\00\00>\00\00\00\00\00\00\00\00\00\00\00.\00.\00/\00.\00.\00/\00r\00u\00n\00t\00i\00m\00e\00/\00a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s\00")
|
||||
(data (i32.const 8) "\10\00\00\00\1e\00\00\00\00\00\00\00\00\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s\00")
|
||||
(data (i32.const 56) "\10\00\00\00 \00\00\00\00\00\00\00\00\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00n\00d\00e\00x\00.\00t\00s\00")
|
||||
(table $0 1 funcref)
|
||||
(elem (i32.const 0) $null)
|
||||
(global $../../runtime/assembly/index/ROOT (mut i32) (i32.const 0))
|
||||
(global $../../runtime/assembly/index/ACYCLIC_FLAG i32 (i32.const 0))
|
||||
(global $~lib/memory/HEAP_BASE i32 (i32.const 88))
|
||||
(global $~lib/rt/pure/ACYCLIC_FLAG i32 (i32.const 0))
|
||||
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
|
||||
(global $~lib/memory/HEAP_BASE i32 (i32.const 104))
|
||||
(export "memory" (memory $0))
|
||||
(export "memory.allocate" (func $assembly/index/memory.allocate))
|
||||
(export "memory.free" (func $assembly/index/memory.free))
|
||||
(export "memory.fill" (func $assembly/index/memory.fill))
|
||||
(func $../../runtime/assembly/index/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -41,7 +41,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 276
|
||||
i32.const 265
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -66,7 +66,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 278
|
||||
i32.const 267
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -118,7 +118,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 291
|
||||
i32.const 280
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -142,7 +142,7 @@
|
||||
i32.store offset=16
|
||||
end
|
||||
local.get $1
|
||||
block $../../runtime/assembly/index/GETHEAD|inlined.1 (result i32)
|
||||
block $~lib/rt/tlsf/GETHEAD|inlined.1 (result i32)
|
||||
local.get $0
|
||||
local.set $10
|
||||
local.get $4
|
||||
@ -162,7 +162,7 @@
|
||||
end
|
||||
i32.eq
|
||||
if
|
||||
block $../../runtime/assembly/index/SETHEAD|inlined.1
|
||||
block $~lib/rt/tlsf/SETHEAD|inlined.1
|
||||
local.get $0
|
||||
local.set $11
|
||||
local.get $4
|
||||
@ -186,7 +186,7 @@
|
||||
local.get $7
|
||||
i32.eqz
|
||||
if
|
||||
block $../../runtime/assembly/index/GETSL|inlined.0 (result i32)
|
||||
block $~lib/rt/tlsf/GETSL|inlined.0 (result i32)
|
||||
local.get $0
|
||||
local.set $9
|
||||
local.get $4
|
||||
@ -199,7 +199,7 @@
|
||||
i32.load offset=4
|
||||
end
|
||||
local.set $8
|
||||
block $../../runtime/assembly/index/SETSL|inlined.1
|
||||
block $~lib/rt/tlsf/SETSL|inlined.1
|
||||
local.get $0
|
||||
local.set $11
|
||||
local.get $4
|
||||
@ -238,7 +238,7 @@
|
||||
end
|
||||
end
|
||||
)
|
||||
(func $../../runtime/assembly/index/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -256,7 +256,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 204
|
||||
i32.const 193
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -271,12 +271,12 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 206
|
||||
i32.const 195
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
end
|
||||
block $../../runtime/assembly/index/GETRIGHT|inlined.0 (result i32)
|
||||
block $~lib/rt/tlsf/GETRIGHT|inlined.0 (result i32)
|
||||
local.get $1
|
||||
local.set $3
|
||||
local.get $3
|
||||
@ -318,7 +318,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $4
|
||||
call $../../runtime/assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $1
|
||||
local.get $2
|
||||
i32.const 3
|
||||
@ -327,7 +327,7 @@
|
||||
i32.or
|
||||
local.tee $2
|
||||
i32.store
|
||||
block $../../runtime/assembly/index/GETRIGHT|inlined.1 (result i32)
|
||||
block $~lib/rt/tlsf/GETRIGHT|inlined.1 (result i32)
|
||||
local.get $1
|
||||
local.set $6
|
||||
local.get $6
|
||||
@ -351,7 +351,7 @@
|
||||
i32.const 2
|
||||
i32.and
|
||||
if
|
||||
block $../../runtime/assembly/index/GETFREELEFT|inlined.0 (result i32)
|
||||
block $~lib/rt/tlsf/GETFREELEFT|inlined.0 (result i32)
|
||||
local.get $1
|
||||
local.set $3
|
||||
local.get $3
|
||||
@ -370,7 +370,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 227
|
||||
i32.const 216
|
||||
i32.const 15
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -395,7 +395,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $../../runtime/assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $3
|
||||
local.get $6
|
||||
i32.const 3
|
||||
@ -433,7 +433,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 242
|
||||
i32.const 231
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -449,7 +449,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 243
|
||||
i32.const 232
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -506,12 +506,12 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 259
|
||||
i32.const 248
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
end
|
||||
block $../../runtime/assembly/index/GETHEAD|inlined.2 (result i32)
|
||||
block $~lib/rt/tlsf/GETHEAD|inlined.2 (result i32)
|
||||
local.get $0
|
||||
local.set $3
|
||||
local.get $9
|
||||
@ -542,7 +542,7 @@
|
||||
local.get $1
|
||||
i32.store offset=16
|
||||
end
|
||||
block $../../runtime/assembly/index/SETHEAD|inlined.2
|
||||
block $~lib/rt/tlsf/SETHEAD|inlined.2
|
||||
local.get $0
|
||||
local.set $12
|
||||
local.get $9
|
||||
@ -571,8 +571,8 @@
|
||||
i32.shl
|
||||
i32.or
|
||||
i32.store
|
||||
block $../../runtime/assembly/index/SETSL|inlined.2
|
||||
block $../../runtime/assembly/index/GETSL|inlined.1 (result i32)
|
||||
block $~lib/rt/tlsf/SETSL|inlined.2
|
||||
block $~lib/rt/tlsf/GETSL|inlined.1 (result i32)
|
||||
local.get $0
|
||||
local.set $13
|
||||
local.get $9
|
||||
@ -598,7 +598,7 @@
|
||||
i32.store offset=4
|
||||
end
|
||||
)
|
||||
(func $../../runtime/assembly/index/addMemory (; 3 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/addMemory (; 3 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
@ -629,12 +629,12 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 385
|
||||
i32.const 374
|
||||
i32.const 4
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
end
|
||||
block $../../runtime/assembly/index/GETTAIL|inlined.0 (result i32)
|
||||
block $~lib/rt/tlsf/GETTAIL|inlined.0 (result i32)
|
||||
local.get $0
|
||||
local.set $3
|
||||
local.get $3
|
||||
@ -654,7 +654,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 395
|
||||
i32.const 384
|
||||
i32.const 15
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -685,7 +685,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 407
|
||||
i32.const 396
|
||||
i32.const 4
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -740,7 +740,7 @@
|
||||
i32.const 2
|
||||
i32.or
|
||||
i32.store
|
||||
block $../../runtime/assembly/index/SETTAIL|inlined.1
|
||||
block $~lib/rt/tlsf/SETTAIL|inlined.1
|
||||
local.get $0
|
||||
local.set $9
|
||||
local.get $4
|
||||
@ -751,10 +751,10 @@
|
||||
end
|
||||
local.get $0
|
||||
local.get $8
|
||||
call $../../runtime/assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
i32.const 1
|
||||
)
|
||||
(func $../../runtime/assembly/index/initialize (; 4 ;) (type $FUNCSIG$i) (result i32)
|
||||
(func $~lib/rt/tlsf/initializeRoot (; 4 ;) (type $FUNCSIG$v)
|
||||
(local $0 i32)
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
@ -808,7 +808,7 @@
|
||||
local.get $3
|
||||
i32.const 0
|
||||
i32.store
|
||||
block $../../runtime/assembly/index/SETTAIL|inlined.0
|
||||
block $~lib/rt/tlsf/SETTAIL|inlined.0
|
||||
local.get $3
|
||||
local.set $5
|
||||
i32.const 0
|
||||
@ -827,7 +827,7 @@
|
||||
i32.eqz
|
||||
br_if $break|0
|
||||
block
|
||||
block $../../runtime/assembly/index/SETSL|inlined.0
|
||||
block $~lib/rt/tlsf/SETSL|inlined.0
|
||||
local.get $3
|
||||
local.set $7
|
||||
local.get $4
|
||||
@ -851,7 +851,7 @@
|
||||
i32.lt_u
|
||||
i32.eqz
|
||||
br_if $break|1
|
||||
block $../../runtime/assembly/index/SETHEAD|inlined.0
|
||||
block $~lib/rt/tlsf/SETHEAD|inlined.0
|
||||
local.get $3
|
||||
local.set $9
|
||||
local.get $4
|
||||
@ -904,11 +904,12 @@
|
||||
current_memory
|
||||
i32.const 16
|
||||
i32.shl
|
||||
call $../../runtime/assembly/index/addMemory
|
||||
call $~lib/rt/tlsf/addMemory
|
||||
drop
|
||||
local.get $3
|
||||
global.set $~lib/rt/tlsf/ROOT
|
||||
)
|
||||
(func $../../runtime/assembly/index/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
local.get $0
|
||||
@ -917,7 +918,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 466
|
||||
i32.const 436
|
||||
i32.const 29
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -937,7 +938,7 @@
|
||||
i32.gt_u
|
||||
select
|
||||
)
|
||||
(func $../../runtime/assembly/index/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -1011,12 +1012,12 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 337
|
||||
i32.const 326
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
end
|
||||
block $../../runtime/assembly/index/GETSL|inlined.2 (result i32)
|
||||
block $~lib/rt/tlsf/GETSL|inlined.2 (result i32)
|
||||
local.get $0
|
||||
local.set $5
|
||||
local.get $2
|
||||
@ -1058,7 +1059,7 @@
|
||||
local.get $4
|
||||
i32.ctz
|
||||
local.set $2
|
||||
block $../../runtime/assembly/index/GETSL|inlined.3 (result i32)
|
||||
block $~lib/rt/tlsf/GETSL|inlined.3 (result i32)
|
||||
local.get $0
|
||||
local.set $8
|
||||
local.get $2
|
||||
@ -1076,12 +1077,12 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 350
|
||||
i32.const 339
|
||||
i32.const 17
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
end
|
||||
block $../../runtime/assembly/index/GETHEAD|inlined.3 (result i32)
|
||||
block $~lib/rt/tlsf/GETHEAD|inlined.3 (result i32)
|
||||
local.get $0
|
||||
local.set $9
|
||||
local.get $2
|
||||
@ -1103,7 +1104,7 @@
|
||||
local.set $7
|
||||
end
|
||||
else
|
||||
block $../../runtime/assembly/index/GETHEAD|inlined.4 (result i32)
|
||||
block $~lib/rt/tlsf/GETHEAD|inlined.4 (result i32)
|
||||
local.get $0
|
||||
local.set $8
|
||||
local.get $2
|
||||
@ -1126,7 +1127,7 @@
|
||||
end
|
||||
local.get $7
|
||||
)
|
||||
(func $../../runtime/assembly/index/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -1176,10 +1177,10 @@
|
||||
local.get $7
|
||||
i32.const 16
|
||||
i32.shl
|
||||
call $../../runtime/assembly/index/addMemory
|
||||
call $~lib/rt/tlsf/addMemory
|
||||
drop
|
||||
)
|
||||
(func $../../runtime/assembly/index/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(func $~lib/rt/tlsf/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
@ -1194,7 +1195,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 364
|
||||
i32.const 353
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -1235,7 +1236,7 @@
|
||||
i32.store
|
||||
local.get $0
|
||||
local.get $5
|
||||
call $../../runtime/assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
else
|
||||
local.get $1
|
||||
local.get $3
|
||||
@ -1244,7 +1245,7 @@
|
||||
i32.xor
|
||||
i32.and
|
||||
i32.store
|
||||
block $../../runtime/assembly/index/GETRIGHT|inlined.3 (result i32)
|
||||
block $~lib/rt/tlsf/GETRIGHT|inlined.3 (result i32)
|
||||
local.get $1
|
||||
local.set $5
|
||||
local.get $5
|
||||
@ -1258,7 +1259,7 @@
|
||||
i32.and
|
||||
i32.add
|
||||
end
|
||||
block $../../runtime/assembly/index/GETRIGHT|inlined.2 (result i32)
|
||||
block $~lib/rt/tlsf/GETRIGHT|inlined.2 (result i32)
|
||||
local.get $1
|
||||
local.set $5
|
||||
local.get $5
|
||||
@ -1280,32 +1281,32 @@
|
||||
i32.store
|
||||
end
|
||||
)
|
||||
(func $../../runtime/assembly/index/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
local.get $1
|
||||
call $../../runtime/assembly/index/prepareSize
|
||||
call $~lib/rt/tlsf/prepareSize
|
||||
local.set $2
|
||||
local.get $0
|
||||
local.get $2
|
||||
call $../../runtime/assembly/index/searchBlock
|
||||
call $~lib/rt/tlsf/searchBlock
|
||||
local.set $3
|
||||
local.get $3
|
||||
i32.eqz
|
||||
if
|
||||
local.get $0
|
||||
local.get $2
|
||||
call $../../runtime/assembly/index/growMemory
|
||||
call $~lib/rt/tlsf/growMemory
|
||||
local.get $0
|
||||
local.get $2
|
||||
call $../../runtime/assembly/index/searchBlock
|
||||
call $~lib/rt/tlsf/searchBlock
|
||||
local.set $3
|
||||
local.get $3
|
||||
i32.eqz
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 477
|
||||
i32.const 466
|
||||
i32.const 15
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -1323,7 +1324,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 479
|
||||
i32.const 468
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -1339,35 +1340,35 @@
|
||||
i32.store offset=12
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $../../runtime/assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $0
|
||||
local.get $3
|
||||
local.get $2
|
||||
call $../../runtime/assembly/index/prepareBlock
|
||||
call $~lib/rt/tlsf/prepareBlock
|
||||
local.get $3
|
||||
)
|
||||
(func $../../runtime/assembly/index/__mm_allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(func $~lib/rt/index/__mm_allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(local $1 i32)
|
||||
global.get $../../runtime/assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.set $1
|
||||
local.get $1
|
||||
i32.eqz
|
||||
if
|
||||
call $../../runtime/assembly/index/initialize
|
||||
local.tee $1
|
||||
global.set $../../runtime/assembly/index/ROOT
|
||||
call $~lib/rt/tlsf/initializeRoot
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.set $1
|
||||
end
|
||||
local.get $1
|
||||
local.get $0
|
||||
call $../../runtime/assembly/index/allocateBlock
|
||||
call $~lib/rt/tlsf/allocateBlock
|
||||
i32.const 16
|
||||
i32.add
|
||||
)
|
||||
(func $assembly/index/memory.allocate (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
local.get $0
|
||||
call $../../runtime/assembly/index/__mm_allocate
|
||||
call $~lib/rt/index/__mm_allocate
|
||||
)
|
||||
(func $../../runtime/assembly/index/freeBlock (; 12 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/freeBlock (; 12 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
local.get $1
|
||||
i32.load
|
||||
@ -1380,7 +1381,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 530
|
||||
i32.const 519
|
||||
i32.const 2
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -1392,15 +1393,15 @@
|
||||
i32.store
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $../../runtime/assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
)
|
||||
(func $../../runtime/assembly/index/__mm_free (; 13 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
global.get $../../runtime/assembly/index/ROOT
|
||||
(func $~lib/rt/index/__mm_free (; 13 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
i32.eqz
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 556
|
||||
i32.const 72
|
||||
i32.const 29
|
||||
i32.const 13
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -1419,21 +1420,21 @@
|
||||
i32.eqz
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 557
|
||||
i32.const 72
|
||||
i32.const 30
|
||||
i32.const 2
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
end
|
||||
global.get $../../runtime/assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.get $0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
call $../../runtime/assembly/index/freeBlock
|
||||
call $~lib/rt/tlsf/freeBlock
|
||||
)
|
||||
(func $assembly/index/memory.free (; 14 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
local.get $0
|
||||
call $../../runtime/assembly/index/__mm_free
|
||||
call $~lib/rt/index/__mm_free
|
||||
)
|
||||
(func $~lib/memory/memory.fill (; 15 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
|
@ -1,823 +1,12 @@
|
||||
// An experimental standalone AssemblyScript runtime based on TLSF and PureRC.
|
||||
import "rt";
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const DEBUG = true;
|
||||
|
||||
// Alignment guarantees
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const AL_BITS: u32 = 4; // 16 bytes to fit up to v128
|
||||
// @ts-ignore: decorator
|
||||
@inline const AL_SIZE: usize = 1 << <usize>AL_BITS;
|
||||
// @ts-ignore: decorator
|
||||
@inline const AL_MASK: usize = AL_SIZE - 1;
|
||||
|
||||
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
|
||||
// see: http://www.gii.upv.es/tlsf/
|
||||
|
||||
// - `ffs(x)` is equivalent to `ctz(x)` with x != 0
|
||||
// - `fls(x)` is equivalent to `sizeof(x) * 8 - clz(x) - 1`
|
||||
|
||||
// ╒══════════════ Block size interpretation (32-bit) ═════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┴─┴─┴─╫─┴─┴─┴─┤
|
||||
// │ | FL │ SB = SL + AL │ ◄─ usize
|
||||
// └───────────────────────────────────────────────┴───────╨───────┘
|
||||
// FL: first level, SL: second level, AL: alignment, SB: small block
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const SL_BITS: u32 = 4;
|
||||
// @ts-ignore: decorator
|
||||
@inline const SL_SIZE: usize = 1 << <usize>SL_BITS;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const SB_BITS: usize = <usize>(SL_BITS + AL_BITS);
|
||||
// @ts-ignore: decorator
|
||||
@inline const SB_SIZE: usize = 1 << <usize>SB_BITS;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const FL_BITS: u32 = 31 - SB_BITS;
|
||||
|
||||
// [00]: < 256B (SB) [12]: < 1M
|
||||
// [01]: < 512B [13]: < 2M
|
||||
// [02]: < 1K [14]: < 4M
|
||||
// [03]: < 2K [15]: < 8M
|
||||
// [04]: < 4K [16]: < 16M
|
||||
// [05]: < 8K [17]: < 32M
|
||||
// [06]: < 16K [18]: < 64M
|
||||
// [07]: < 32K [19]: < 128M
|
||||
// [08]: < 64K [20]: < 256M
|
||||
// [09]: < 128K [21]: < 512M
|
||||
// [10]: < 256K [22]: <= 1G - OVERHEAD
|
||||
// [11]: < 512K
|
||||
// VMs limit to 2GB total (currently), making one 1G block max (or three 512M etc.) due to block overhead
|
||||
|
||||
// Tags stored in otherwise unused alignment bits
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const FREE: usize = 1 << 0;
|
||||
// @ts-ignore: decorator
|
||||
@inline const LEFTFREE: usize = 1 << 1;
|
||||
// @ts-ignore: decorator
|
||||
@inline const TAGS_MASK: usize = FREE | LEFTFREE; // <= AL_MASK
|
||||
|
||||
// ╒════════════════════ Block layout (32-bit) ════════════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┼─┤ overhead ┐
|
||||
// │ size │0│L│F│ ◄─┐ info
|
||||
// ├─────────────────────────────────────────────────────────┴─┴─┴─┤ │
|
||||
// │ │ │
|
||||
// │ ... additional runtime overhead ... │ │
|
||||
// │ │ │
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │ ┐ ┘
|
||||
// │ if free: ◄ prev │ ◄─┤ usize
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ if free: next ► │ ◄─┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ ... │ │ = 0
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ if free: back ▲ │ ◄─┘
|
||||
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
|
||||
// F: FREE, L: LEFTFREE
|
||||
@unmanaged class Block {
|
||||
|
||||
/** Memory manager info. */
|
||||
mmInfo: usize; // WASM64 might need adaption
|
||||
/** Garbage collector info. */
|
||||
gcInfo: u32;
|
||||
/** Runtime class id. */
|
||||
rtId: u32;
|
||||
/** Runtime object size. */
|
||||
rtSize: u32;
|
||||
|
||||
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
|
||||
prev: Block | null;
|
||||
/** Next free block, if any. Only valid if free, otherwise part of payload. */
|
||||
next: Block | null;
|
||||
|
||||
// If the block is free, there is a 'back'reference at its end pointing at its start.
|
||||
}
|
||||
|
||||
// Block constants. Overhead is always present, no matter if free or used. Also, a block must have
|
||||
// a minimum size of three pointers so it can hold `prev`, `next` and `back` if free.
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const BLOCK_OVERHEAD: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
|
||||
// @ts-ignore: decorator
|
||||
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + back
|
||||
// @ts-ignore: decorator
|
||||
@inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive
|
||||
|
||||
/** Gets the left block of a block. Only valid if the left block is free. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function GETFREELEFT(block: Block): Block {
|
||||
return load<Block>(changetype<usize>(block) - sizeof<usize>());
|
||||
}
|
||||
|
||||
/** Gets the right block of of a block by advancing to the right by its size. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function GETRIGHT(block: Block): Block {
|
||||
return changetype<Block>(changetype<usize>(block) + BLOCK_OVERHEAD + (block.mmInfo & ~TAGS_MASK));
|
||||
}
|
||||
|
||||
// ╒═════════════════════ Root layout (32-bit) ════════════════════╕
|
||||
// 3 2 1
|
||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
|
||||
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤ ┐
|
||||
// │ 0 | flMap S│ ◄────┐
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │
|
||||
// │ slMap[0] S │ ◄─┐ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ │ │
|
||||
// │ slMap[1] │ ◄─┤ │
|
||||
// ├───────────────────────────────────────────────────────────────┤ u32 │
|
||||
// │ slMap[22] │ ◄─┘ │
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ usize
|
||||
// │ head[0] │ ◄────┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ ... │ ◄────┤
|
||||
// ├───────────────────────────────────────────────────────────────┤ │
|
||||
// │ head[367] │ ◄────┤
|
||||
// ╞═══════════════════════════════════════════════════════════════╡ │
|
||||
// │ tail │ ◄────┘
|
||||
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
|
||||
// S: Small blocks map
|
||||
@unmanaged class Root {
|
||||
/** First level bitmap. */
|
||||
flMap: usize;
|
||||
}
|
||||
|
||||
// Root constants. Where stuff is stored inside of the root structure.
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const SL_START = sizeof<usize>();
|
||||
// @ts-ignore: decorator
|
||||
@inline const SL_END = SL_START + (FL_BITS << alignof<u32>());
|
||||
// @ts-ignore: decorator
|
||||
@inline const HL_START = (SL_END + AL_MASK) & ~AL_MASK;
|
||||
// @ts-ignore: decorator
|
||||
@inline const HL_END = HL_START + FL_BITS * SL_SIZE * sizeof<usize>();
|
||||
// @ts-ignore: decorator
|
||||
@inline const ROOT_SIZE = HL_END + sizeof<usize>();
|
||||
|
||||
var ROOT: Root;
|
||||
|
||||
/** Gets the second level map of the specified first level. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function GETSL(root: Root, fl: usize): u32 {
|
||||
return load<u32>(changetype<usize>(root) + (fl << alignof<u32>()), SL_START);
|
||||
}
|
||||
|
||||
/** Sets the second level map of the specified first level. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function SETSL(root: Root, fl: usize, slMap: u32): void {
|
||||
store<u32>(changetype<usize>(root) + (fl << alignof<u32>()), slMap, SL_START);
|
||||
}
|
||||
|
||||
/** Gets the head of the free list for the specified combination of first and second level. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function GETHEAD(root: Root, fl: usize, sl: u32): Block | null {
|
||||
return changetype<Block>(load<usize>(changetype<usize>(root) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>(), HL_START));
|
||||
}
|
||||
|
||||
/** Sets the head of the free list for the specified combination of first and second level. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function SETHEAD(root: Root, fl: usize, sl: u32, head: Block | null): void {
|
||||
store<usize>(changetype<usize>(root) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>() , changetype<usize>(head), HL_START);
|
||||
}
|
||||
|
||||
/** Gets the tail block.. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function GETTAIL(root: Root): Block {
|
||||
return load<Block>(changetype<usize>(root), HL_END);
|
||||
}
|
||||
|
||||
/** Sets the tail block. */
|
||||
// @ts-ignore: decorator
|
||||
@inline function SETTAIL(root: Root, tail: Block): void {
|
||||
store<Block>(changetype<usize>(root), tail, HL_END);
|
||||
}
|
||||
|
||||
/** Inserts a previously used block back into the free list. */
|
||||
function insertBlock(root: Root, block: Block): void {
|
||||
if (DEBUG) assert(block); // cannot be null
|
||||
var blockInfo = block.mmInfo;
|
||||
if (DEBUG) assert(blockInfo & FREE); // must be free
|
||||
|
||||
var right = GETRIGHT(block);
|
||||
var rightInfo = right.mmInfo;
|
||||
|
||||
// merge with right block if also free
|
||||
if (rightInfo & FREE) {
|
||||
let newSize = (blockInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (rightInfo & ~TAGS_MASK);
|
||||
if (newSize < BLOCK_MAXSIZE) {
|
||||
removeBlock(root, right);
|
||||
block.mmInfo = blockInfo = (blockInfo & TAGS_MASK) | newSize;
|
||||
right = GETRIGHT(block);
|
||||
rightInfo = right.mmInfo;
|
||||
// 'back' is set below
|
||||
}
|
||||
}
|
||||
|
||||
// merge with left block if also free
|
||||
if (blockInfo & LEFTFREE) {
|
||||
let left = GETFREELEFT(block);
|
||||
let leftInfo = left.mmInfo;
|
||||
if (DEBUG) assert(leftInfo & FREE); // must be free according to right tags
|
||||
let newSize = (leftInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (blockInfo & ~TAGS_MASK);
|
||||
if (newSize < BLOCK_MAXSIZE) {
|
||||
removeBlock(root, left);
|
||||
left.mmInfo = blockInfo = (leftInfo & TAGS_MASK) | newSize;
|
||||
block = left;
|
||||
// 'back' is set below
|
||||
}
|
||||
}
|
||||
|
||||
right.mmInfo = rightInfo | LEFTFREE;
|
||||
// right is no longer used now, hence rightInfo is not synced
|
||||
|
||||
// we now know the size of the block
|
||||
var size = blockInfo & ~TAGS_MASK;
|
||||
if (DEBUG) assert(size >= BLOCK_MINSIZE && size < BLOCK_MAXSIZE); // must be a valid size
|
||||
if (DEBUG) assert(changetype<usize>(block) + BLOCK_OVERHEAD + size == changetype<usize>(right)); // must match
|
||||
|
||||
// set 'back' to itself at the end of block
|
||||
store<Block>(changetype<usize>(right) - sizeof<usize>(), block);
|
||||
|
||||
// mapping_insert
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
const inv: usize = sizeof<usize>() * 8 - 1;
|
||||
fl = inv - clz<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
|
||||
|
||||
// perform insertion
|
||||
var head = GETHEAD(root, fl, sl);
|
||||
block.prev = null;
|
||||
block.next = head;
|
||||
if (head) head.prev = block;
|
||||
SETHEAD(root, fl, sl, block);
|
||||
|
||||
// update first and second level maps
|
||||
root.flMap |= (1 << fl);
|
||||
SETSL(root, fl, GETSL(root, fl) | (1 << sl));
|
||||
}
|
||||
|
||||
/** Removes a free block from internal lists. */
|
||||
function removeBlock(root: Root, block: Block): void {
|
||||
var blockInfo = block.mmInfo;
|
||||
if (DEBUG) assert(blockInfo & FREE); // must be free
|
||||
var size = blockInfo & ~TAGS_MASK;
|
||||
if (DEBUG) assert(size >= BLOCK_MINSIZE && size < BLOCK_MAXSIZE); // must be valid
|
||||
|
||||
// mapping_insert
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
const inv: usize = sizeof<usize>() * 8 - 1;
|
||||
fl = inv - clz<usize>(size);
|
||||
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
|
||||
|
||||
// link previous and next free block
|
||||
var prev = block.prev;
|
||||
var next = block.next;
|
||||
if (prev) prev.next = next;
|
||||
if (next) next.prev = prev;
|
||||
|
||||
// update head if we are removing it
|
||||
if (block == GETHEAD(root, fl, sl)) {
|
||||
SETHEAD(root, fl, sl, next);
|
||||
|
||||
// clear second level map if head is empty now
|
||||
if (!next) {
|
||||
let slMap = GETSL(root, fl);
|
||||
SETSL(root, fl, slMap &= ~(1 << sl));
|
||||
|
||||
// clear first level map if second level is empty now
|
||||
if (!slMap) root.flMap &= ~(1 << fl);
|
||||
}
|
||||
}
|
||||
// note: does not alter left/back because it is likely that splitting
|
||||
// is performed afterwards, invalidating those changes. so, the caller
|
||||
// must perform those updates.
|
||||
}
|
||||
|
||||
/** Searches for a free block of at least the specified size. */
|
||||
function searchBlock(root: Root, size: usize): Block | null {
|
||||
// size was already asserted by caller
|
||||
|
||||
// mapping_search
|
||||
var fl: usize, sl: u32;
|
||||
if (size < SB_SIZE) {
|
||||
fl = 0;
|
||||
sl = <u32>(size / AL_SIZE);
|
||||
} else {
|
||||
const halfMaxSize = BLOCK_MAXSIZE >> 1; // don't round last fl
|
||||
const inv: usize = sizeof<usize>() * 8 - 1;
|
||||
const invRound = inv - SL_BITS;
|
||||
let requestSize = size < halfMaxSize
|
||||
? size + (1 << (invRound - clz<usize>(size))) - 1
|
||||
: size;
|
||||
fl = inv - clz<usize>(requestSize);
|
||||
sl = <u32>((requestSize >> (fl - SL_BITS)) ^ (1 << SL_BITS));
|
||||
fl -= SB_BITS - 1;
|
||||
}
|
||||
if (DEBUG) assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
|
||||
|
||||
// search second level
|
||||
var slMap = GETSL(root, fl) & (~0 << sl);
|
||||
var head: Block | null;
|
||||
if (!slMap) {
|
||||
// search next larger first level
|
||||
let flMap = root.flMap & (~0 << (fl + 1));
|
||||
if (!flMap) {
|
||||
head = null;
|
||||
} else {
|
||||
fl = ctz<usize>(flMap);
|
||||
slMap = GETSL(root, fl);
|
||||
if (DEBUG) assert(slMap); // can't be zero if fl points here
|
||||
head = GETHEAD(root, fl, ctz<u32>(slMap));
|
||||
}
|
||||
} else {
|
||||
head = GETHEAD(root, fl, ctz<u32>(slMap));
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
/** Prepares the specified block before (re-)use, possibly splitting it. */
|
||||
function prepareBlock(root: Root, block: Block, size: usize): void {
|
||||
// size was already asserted by caller
|
||||
|
||||
var blockInfo = block.mmInfo;
|
||||
if (DEBUG) assert(!(size & AL_MASK)); // size must be aligned so the new block is
|
||||
|
||||
// split if the block can hold another MINSIZE block incl. overhead
|
||||
var remaining = (blockInfo & ~TAGS_MASK) - size;
|
||||
if (remaining >= BLOCK_OVERHEAD + BLOCK_MINSIZE) {
|
||||
block.mmInfo = size | (blockInfo & LEFTFREE); // also discards FREE
|
||||
|
||||
let spare = changetype<Block>(changetype<usize>(block) + BLOCK_OVERHEAD + size);
|
||||
spare.mmInfo = (remaining - BLOCK_OVERHEAD) | FREE; // not LEFTFREE
|
||||
insertBlock(root, spare); // also sets 'back'
|
||||
|
||||
// otherwise tag block as no longer FREE and right as no longer LEFTFREE
|
||||
} else {
|
||||
block.mmInfo = blockInfo & ~FREE;
|
||||
GETRIGHT(block).mmInfo &= ~LEFTFREE;
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds more memory to the pool. */
|
||||
function addMemory(root: Root, start: usize, end: usize): bool {
|
||||
if (DEBUG) {
|
||||
assert(
|
||||
start <= end && // must be valid
|
||||
!(start & AL_MASK) && // must be aligned
|
||||
!(end & AL_MASK) // must be aligned
|
||||
);
|
||||
}
|
||||
|
||||
var tail = GETTAIL(root);
|
||||
var tailInfo: usize = 0;
|
||||
if (tail) { // more memory
|
||||
if (DEBUG) assert(start >= changetype<usize>(tail) + BLOCK_OVERHEAD);
|
||||
|
||||
// merge with current tail if adjacent
|
||||
if (start - BLOCK_OVERHEAD == changetype<usize>(tail)) {
|
||||
start -= BLOCK_OVERHEAD;
|
||||
tailInfo = tail.mmInfo;
|
||||
} else {
|
||||
// We don't do this, but a user might `memory.grow` manually
|
||||
// leading to non-adjacent pages managed by TLSF.
|
||||
}
|
||||
|
||||
} else if (DEBUG) { // first memory
|
||||
assert(start >= changetype<usize>(root) + ROOT_SIZE); // starts after root
|
||||
}
|
||||
|
||||
// check if size is large enough for a free block and the tail block
|
||||
var size = end - start;
|
||||
if (size < BLOCK_OVERHEAD + BLOCK_MINSIZE + BLOCK_OVERHEAD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// left size is total minus its own and the zero-length tail's header
|
||||
var leftSize = size - 2 * BLOCK_OVERHEAD;
|
||||
var left = changetype<Block>(start);
|
||||
left.mmInfo = leftSize | FREE | (tailInfo & LEFTFREE);
|
||||
left.prev = null;
|
||||
left.next = null;
|
||||
|
||||
// tail is a zero-length used block
|
||||
tail = changetype<Block>(start + size - BLOCK_OVERHEAD);
|
||||
tail.mmInfo = 0 | LEFTFREE;
|
||||
SETTAIL(root, tail);
|
||||
|
||||
insertBlock(root, left); // also merges with free left before tail / sets 'back'
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Grows memory to fit at least another block of the specified size. */
|
||||
function growMemory(root: Root, size: usize): void {
|
||||
var pagesBefore = memory.size();
|
||||
var pagesNeeded = <i32>(((size + 0xffff) & ~0xffff) >>> 16);
|
||||
var pagesWanted = max(pagesBefore, pagesNeeded); // double memory
|
||||
if (memory.grow(pagesWanted) < 0) {
|
||||
if (memory.grow(pagesNeeded) < 0) unreachable();
|
||||
}
|
||||
var pagesAfter = memory.size();
|
||||
addMemory(root, <usize>pagesBefore << 16, <usize>pagesAfter << 16);
|
||||
}
|
||||
|
||||
/** Initilizes the root structure. */
|
||||
function initialize(): Root {
|
||||
var rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
|
||||
var pagesBefore = memory.size();
|
||||
var pagesNeeded = <i32>((((rootOffset + ROOT_SIZE) + 0xffff) & ~0xffff) >>> 16);
|
||||
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
|
||||
var root = changetype<Root>(rootOffset);
|
||||
root.flMap = 0;
|
||||
SETTAIL(root, changetype<Block>(0));
|
||||
for (let fl: usize = 0; fl < FL_BITS; ++fl) {
|
||||
SETSL(root, fl, 0);
|
||||
for (let sl: u32 = 0; sl < SL_SIZE; ++sl) {
|
||||
SETHEAD(root, fl, sl, null);
|
||||
}
|
||||
}
|
||||
addMemory(root, (rootOffset + ROOT_SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16);
|
||||
return root;
|
||||
}
|
||||
|
||||
/** Prepares and checks an allocation size. */
|
||||
function prepareSize(size: usize): usize {
|
||||
if (size >= BLOCK_MAXSIZE) throw new Error("allocation too large");
|
||||
return max<usize>((size + AL_MASK) & ~AL_MASK, BLOCK_MINSIZE); // align and ensure min size
|
||||
}
|
||||
|
||||
/** Allocates a block of the specified size. */
|
||||
function allocateBlock(root: Root, size: usize): Block {
|
||||
var payloadSize = prepareSize(size);
|
||||
var block = searchBlock(root, payloadSize);
|
||||
if (!block) {
|
||||
growMemory(root, payloadSize);
|
||||
block = <Block>searchBlock(root, payloadSize);
|
||||
if (DEBUG) assert(block); // must be found now
|
||||
}
|
||||
if (DEBUG) assert((block.mmInfo & ~TAGS_MASK) >= payloadSize); // must fit
|
||||
block.gcInfo = 0;
|
||||
block.rtId = 0; // not determined yet
|
||||
block.rtSize = size;
|
||||
removeBlock(root, <Block>block);
|
||||
prepareBlock(root, <Block>block, payloadSize);
|
||||
return <Block>block;
|
||||
}
|
||||
|
||||
/** Reallocates a block to the specified size. */
|
||||
function reallocateBlock(root: Root, block: Block, size: usize): Block {
|
||||
var payloadSize = prepareSize(size);
|
||||
var blockInfo = block.mmInfo;
|
||||
if (DEBUG) assert(!(blockInfo & FREE)); // must be used
|
||||
|
||||
// possibly split and update runtime size if it still fits
|
||||
if (payloadSize <= (blockInfo & ~TAGS_MASK)) {
|
||||
prepareBlock(root, block, payloadSize);
|
||||
block.rtSize = size;
|
||||
return block;
|
||||
}
|
||||
|
||||
// merge with right free block if merger is large enough
|
||||
var right = GETRIGHT(block);
|
||||
var rightInfo = right.mmInfo;
|
||||
if (rightInfo & FREE) {
|
||||
let mergeSize = (blockInfo & ~TAGS_MASK) + BLOCK_OVERHEAD + (rightInfo & ~TAGS_MASK);
|
||||
if (mergeSize >= payloadSize) {
|
||||
removeBlock(root, right);
|
||||
// TODO: this can yield an intermediate block larger than BLOCK_MAXSIZE, which
|
||||
// is immediately split though. does this trigger any assertions / issues?
|
||||
block.mmInfo = (blockInfo & TAGS_MASK) | mergeSize;
|
||||
block.rtSize = size;
|
||||
prepareBlock(root, block, payloadSize);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise move the block
|
||||
var newBlock = allocateBlock(root, size);
|
||||
newBlock.gcInfo = block.gcInfo;
|
||||
newBlock.rtId = block.rtId;
|
||||
memory.copy(changetype<usize>(newBlock) + BLOCK_OVERHEAD, changetype<usize>(block) + BLOCK_OVERHEAD, size);
|
||||
block.mmInfo = blockInfo | FREE;
|
||||
insertBlock(root, block);
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
/** Frees a block. */
|
||||
function freeBlock(root: Root, block: Block): void {
|
||||
var blockInfo = block.mmInfo;
|
||||
assert(!(blockInfo & FREE)); // must be used (user might call through to this)
|
||||
block.mmInfo = blockInfo | FREE;
|
||||
insertBlock(root, block);
|
||||
}
|
||||
|
||||
// Memory manager interface.
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __mm_allocate(size: usize): usize {
|
||||
var root = ROOT;
|
||||
if (!root) ROOT = root = initialize();
|
||||
return changetype<usize>(allocateBlock(root, size)) + BLOCK_OVERHEAD;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __mm_reallocate(data: usize, size: usize): usize {
|
||||
if (DEBUG) assert(ROOT); // must be initialized
|
||||
assert(data != 0 && !(data & AL_MASK)); // must exist and be aligned
|
||||
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(data - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __mm_free(data: usize): void {
|
||||
if (DEBUG) assert(ROOT); // must be initialized
|
||||
assert(data != 0 && !(data & AL_MASK)); // must exist and be aligned
|
||||
freeBlock(ROOT, changetype<Block>(data - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
/////////////////////////// A Pure Reference Counting Garbage Collector ///////////////////////////
|
||||
// see: https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
|
||||
|
||||
// TODO: make visitors eat cookies so we can compile direct calls into a switch
|
||||
function __rt_visit_members(s: Block, cookie: i32): void { unreachable(); }
|
||||
function __rt_flags(classId: u32): u32 { return unreachable(); }
|
||||
const ACYCLIC_FLAG: u32 = 0;
|
||||
|
||||
// ╒══════════════════════ GC Info structure ══════════════════════╕
|
||||
// │ 3 2 1 │
|
||||
// │1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0│
|
||||
// ├─┼─┴─┴─┼─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
|
||||
// │B│color│ refCount │
|
||||
// └─┴─────┴───────────────────────────────────────────────────────┘
|
||||
// B: buffered
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const BUFFERED_MASK: u32 = 1 << (sizeof<u32>() * 8 - 1);
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_BITS = 3;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_SHIFT: u32 = ctz(BUFFERED_MASK) - COLOR_BITS;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_MASK: u32 = ((1 << COLOR_BITS) - 1) << COLOR_SHIFT;
|
||||
// @ts-ignore: decorator
|
||||
@inline const REFCOUNT_MASK: u32 = (1 << COLOR_SHIFT) - 1;
|
||||
|
||||
// ╒════════╤═══════════════════ Colors ═══════════════════════════╕
|
||||
// │ Color │ Meaning │
|
||||
// ├────────┼──────────────────────────────────────────────────────┤
|
||||
// │ BLACK │ In use or free │
|
||||
// │ GRAY │ Possible member of cycle │
|
||||
// │ WHITE │ Member of garbage cycle │
|
||||
// │ PURPLE │ Possible root of cycle │
|
||||
// │ RED │ Candidate cycle undergoing Σ-computation *concurrent │
|
||||
// │ ORANGE │ Candidate cycle awaiting epoch boundary *concurrent │
|
||||
// └────────┴──────────────────────────────────────────────────────┘
|
||||
// Acyclic detection has been decoupled, hence no GREEN.
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_BLACK: u32 = 0 << COLOR_SHIFT;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_GRAY: u32 = 1 << COLOR_SHIFT;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_WHITE: u32 = 2 << COLOR_SHIFT;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_PURPLE: u32 = 3 << COLOR_SHIFT;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_RED: u32 = 4 << COLOR_SHIFT;
|
||||
// @ts-ignore: decorator
|
||||
@inline const COLOR_ORANGE: u32 = 5 << COLOR_SHIFT;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@inline const VISIT_DECREMENT = 1; // guard 0
|
||||
// @ts-ignore: decorator
|
||||
@inline const VISIT_MARKGRAY = 2;
|
||||
// @ts-ignore: decorator
|
||||
@inline const VISIT_SCAN = 3;
|
||||
// @ts-ignore: decorator
|
||||
@inline const VISIT_SCANBLACK = 4;
|
||||
// @ts-ignore: decorator
|
||||
@inline const VISIT_COLLECTWHITE = 5;
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global
|
||||
function __rt_visit(s: Block, cookie: i32): void {
|
||||
switch (cookie) {
|
||||
case VISIT_DECREMENT: {
|
||||
decrement(s);
|
||||
break;
|
||||
}
|
||||
case VISIT_MARKGRAY: {
|
||||
if (DEBUG) assert((s.gcInfo & REFCOUNT_MASK) > 0);
|
||||
s.gcInfo = s.gcInfo - 1;
|
||||
markGray(s);
|
||||
break;
|
||||
}
|
||||
case VISIT_SCAN: {
|
||||
scan(s);
|
||||
break;
|
||||
}
|
||||
case VISIT_SCANBLACK: {
|
||||
let info = s.gcInfo;
|
||||
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
|
||||
s.gcInfo = info + 1;
|
||||
if ((info & COLOR_MASK) != COLOR_BLACK) {
|
||||
scanBlack(s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VISIT_COLLECTWHITE: {
|
||||
collectWhite(s);
|
||||
break;
|
||||
}
|
||||
default: if (DEBUG) assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Increments the reference count of the specified block by one.*/
|
||||
function increment(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
assert((info & ~REFCOUNT_MASK) == ((info + 1) & ~REFCOUNT_MASK)); // overflow
|
||||
s.gcInfo = info + 1;
|
||||
}
|
||||
|
||||
/** Decrements the reference count of the specified block by one, possibly freeing it. */
|
||||
function decrement(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
var rc = info & REFCOUNT_MASK;
|
||||
if (rc == 1) {
|
||||
__rt_visit_members(s, VISIT_DECREMENT);
|
||||
if (!(info & BUFFERED_MASK)) {
|
||||
freeBlock(ROOT, s);
|
||||
} else {
|
||||
s.gcInfo = BUFFERED_MASK | COLOR_BLACK | 0;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) assert(rc > 0);
|
||||
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
|
||||
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
|
||||
if (!(info & BUFFERED_MASK)) {
|
||||
appendRoot(s);
|
||||
}
|
||||
} else {
|
||||
s.gcInfo = (info & ~REFCOUNT_MASK) | (rc - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Buffer of possible roots. */
|
||||
// @ts-ignore: decorator
|
||||
@lazy var ROOTS: usize;
|
||||
/** Current absolute offset into the `ROOTS` buffer. */
|
||||
// @ts-ignore: decorator
|
||||
@lazy var CUR: usize = 0;
|
||||
/** Current absolute end offset into the `ROOTS` buffer. */
|
||||
// @ts-ignore: decorator
|
||||
@lazy var END: usize = 0;
|
||||
|
||||
/** Appends a block to possible roots. */
|
||||
function appendRoot(s: Block): void {
|
||||
var cur = CUR;
|
||||
if (cur >= END) {
|
||||
growRoots(); // TBD: either that or pick a default and force collection on overflow
|
||||
cur = CUR;
|
||||
}
|
||||
store<Block>(cur, s);
|
||||
CUR = cur + 1;
|
||||
}
|
||||
|
||||
/** Grows the roots buffer if it ran full. */
|
||||
function growRoots(): void {
|
||||
var oldRoots = ROOTS;
|
||||
var oldSize = CUR - oldRoots;
|
||||
var newSize = max(oldSize * 2, 64 << alignof<usize>());
|
||||
var newRoots = memory.allocate(newSize);
|
||||
memory.copy(newRoots, oldRoots, oldSize);
|
||||
ROOTS = newRoots;
|
||||
CUR = newRoots + oldSize;
|
||||
END = newRoots + newSize;
|
||||
}
|
||||
|
||||
/** Collects cyclic garbage. */
|
||||
function collectCycles(): void {
|
||||
|
||||
// markRoots
|
||||
var roots = ROOTS;
|
||||
var cur = roots;
|
||||
for (let pos = cur, end = CUR; pos < end; pos += sizeof<usize>()) {
|
||||
let s = load<Block>(pos);
|
||||
let info = s.gcInfo;
|
||||
if ((info & COLOR_MASK) == COLOR_PURPLE && (info & REFCOUNT_MASK) > 0) {
|
||||
markGray(s);
|
||||
store<Block>(cur, s);
|
||||
cur += sizeof<usize>();
|
||||
} else {
|
||||
if ((info & COLOR_MASK) == COLOR_BLACK && !(info & REFCOUNT_MASK)) {
|
||||
freeBlock(ROOT, s);
|
||||
} else {
|
||||
s.gcInfo = info & ~BUFFERED_MASK;
|
||||
}
|
||||
}
|
||||
}
|
||||
CUR = cur;
|
||||
|
||||
// scanRoots
|
||||
for (let pos = roots; pos < cur; pos += sizeof<usize>()) {
|
||||
scan(load<Block>(pos));
|
||||
}
|
||||
|
||||
// collectRoots
|
||||
for (let pos = roots; pos < cur; pos += sizeof<usize>()) {
|
||||
let s = load<Block>(pos);
|
||||
s.gcInfo = s.gcInfo & ~BUFFERED_MASK;
|
||||
collectWhite(s);
|
||||
}
|
||||
CUR = roots;
|
||||
}
|
||||
|
||||
/** Marks a block as gray (possible member of cycle) during the collection phase. */
|
||||
function markGray(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
if ((info & COLOR_MASK) != COLOR_GRAY) {
|
||||
s.gcInfo = (info & ~COLOR_MASK) | COLOR_GRAY;
|
||||
__rt_visit_members(s, VISIT_MARKGRAY);
|
||||
}
|
||||
}
|
||||
|
||||
/** Scans a block during the collection phase, determining whether it is garbage or not. */
|
||||
function scan(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
if ((info & COLOR_MASK) == COLOR_GRAY) {
|
||||
if ((info & REFCOUNT_MASK) > 0) {
|
||||
scanBlack(s);
|
||||
} else {
|
||||
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
|
||||
__rt_visit_members(s, VISIT_SCAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Marks a block as black (in use) if it was found to be reachable during the collection phase. */
|
||||
function scanBlack(s: Block): void {
|
||||
s.gcInfo = (s.gcInfo & ~COLOR_MASK) | COLOR_BLACK;
|
||||
__rt_visit_members(s, VISIT_SCANBLACK);
|
||||
}
|
||||
|
||||
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
|
||||
function collectWhite(s: Block): void {
|
||||
var info = s.gcInfo;
|
||||
if ((info & COLOR_MASK) == COLOR_WHITE && !(info & BUFFERED_MASK)) {
|
||||
// s.gcInfo = (info & ~COLOR_MASK) | COLOR_BLACK;
|
||||
__rt_visit_members(s, VISIT_COLLECTWHITE);
|
||||
}
|
||||
freeBlock(ROOT, s);
|
||||
}
|
||||
|
||||
// Garbage collector interface
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __gc_retain(ref: usize): void {
|
||||
if (ref) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
// @ts-ignore: decorator
|
||||
@global @unsafe
|
||||
function __gc_release(ref: usize): void {
|
||||
if (ref) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
|
||||
}
|
||||
|
||||
// keep alive, everything else is reached from here
|
||||
export {
|
||||
__mm_allocate,
|
||||
__mm_reallocate,
|
||||
__mm_free,
|
||||
__rt_visit,
|
||||
__gc_retain,
|
||||
__gc_release,
|
||||
collectCycles as __gc_collect
|
||||
__gc_collect
|
||||
};
|
||||
|
||||
// @start export function main(): void {}
|
||||
@start export function main(): void {}
|
||||
|
@ -133,6 +133,7 @@ function allocate(size) {
|
||||
el.appendChild(er);
|
||||
var ef = document.createElement("button");
|
||||
ef.innerText = "free";
|
||||
ef.className = "free";
|
||||
el.appendChild(ef);
|
||||
ef.onclick = function() {
|
||||
exports.__mm_free(ptr);
|
||||
@ -154,12 +155,16 @@ button:hover { background: #bbb; }
|
||||
.clear { clear: both; }
|
||||
/* Lists */
|
||||
.fl, .sl, .hl, .seg { float: left; padding: 0.4em; margin: 0.2em; border: 1px solid #ddd; border-radius: 3px; }
|
||||
.fl { min-width: 1.3em; text-align: center; }
|
||||
.sl { min-width: 12em; }
|
||||
.hl { min-width: 7em; font-size: 0.8em; }
|
||||
.num { color: #fff; background: rgba(0, 0, 0, 0.3); padding: 0.4em; margin-left: -0.4em; border-radius: 0.2em; }
|
||||
.set { background: #7f7; }
|
||||
.seg { border-top: 0.3em solid #333; }
|
||||
.seg button { margin-left: 0.5em; }
|
||||
.sub { vertical-align: sub; font-size: 0.8em; }
|
||||
.free { background: #f77; border-color: #c33; }
|
||||
.free:hover { background: #c33; color: #fff; }
|
||||
</style>
|
||||
|
||||
<h1>AssemblyScript Runtime Visualizer / TLSF</h1>
|
||||
|
@ -1,31 +1,39 @@
|
||||
(module
|
||||
(type $FUNCSIG$v (func))
|
||||
(type $FUNCSIG$ii (func (param i32) (result i32)))
|
||||
(type $FUNCSIG$i (func (result i32)))
|
||||
(type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32)))
|
||||
(type $FUNCSIG$vii (func (param i32 i32)))
|
||||
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
|
||||
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
|
||||
(type $FUNCSIG$viii (func (param i32 i32 i32)))
|
||||
(type $FUNCSIG$vi (func (param i32)))
|
||||
(type $FUNCSIG$v (func))
|
||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||
(memory $0 1)
|
||||
(data (i32.const 8) "\10\00\00\00\"")
|
||||
(data (i32.const 24) "a\00s\00s\00e\00m\00b\00l\00y\00/\00i\00n\00d\00e\00x\00.\00t\00s")
|
||||
(data (i32.const 64) "\10\00\00\00\1c")
|
||||
(data (i32.const 80) "~\00l\00i\00b\00/\00m\00e\00m\00o\00r\00y\00.\00t\00s")
|
||||
(global $assembly/index/ROOT (mut i32) (i32.const 0))
|
||||
(global $assembly/index/CUR (mut i32) (i32.const 0))
|
||||
(global $assembly/index/ROOTS (mut i32) (i32.const 0))
|
||||
(data (i32.const 8) "\10\00\00\00\1e")
|
||||
(data (i32.const 24) "~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s")
|
||||
(data (i32.const 56) "\10\00\00\00\1c")
|
||||
(data (i32.const 72) "~\00l\00i\00b\00/\00m\00e\00m\00o\00r\00y\00.\00t\00s")
|
||||
(global $~lib/started (mut i32) (i32.const 0))
|
||||
(global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0))
|
||||
(global $~lib/rt/pure/CUR (mut i32) (i32.const 0))
|
||||
(global $~lib/rt/pure/ROOTS (mut i32) (i32.const 0))
|
||||
(export "memory" (memory $0))
|
||||
(export "__mm_allocate" (func $assembly/index/__mm_allocate))
|
||||
(export "__mm_reallocate" (func $assembly/index/__mm_reallocate))
|
||||
(export "__mm_free" (func $assembly/index/__mm_free))
|
||||
(export "__rt_visit" (func $assembly/index/__rt_visit))
|
||||
(export "__gc_retain" (func $assembly/index/__gc_retain))
|
||||
(export "__gc_release" (func $assembly/index/__gc_release))
|
||||
(export "__gc_collect" (func $assembly/index/collectCycles))
|
||||
(func $assembly/index/removeBlock (; 1 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(export "main" (func $assembly/index/main))
|
||||
(export "__mm_allocate" (func $~lib/rt/index/__mm_allocate))
|
||||
(export "__mm_reallocate" (func $~lib/rt/index/__mm_reallocate))
|
||||
(export "__mm_free" (func $~lib/rt/index/__mm_free))
|
||||
(export "__gc_retain" (func $~lib/rt/index/__gc_retain))
|
||||
(export "__gc_release" (func $~lib/rt/index/__gc_release))
|
||||
(export "__gc_collect" (func $~lib/rt/index/__gc_collect))
|
||||
(func $assembly/index/main (; 1 ;) (type $FUNCSIG$v)
|
||||
global.get $~lib/started
|
||||
i32.eqz
|
||||
if
|
||||
i32.const 1
|
||||
global.set $~lib/started
|
||||
end
|
||||
)
|
||||
(func $~lib/rt/tlsf/removeBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -141,7 +149,7 @@
|
||||
end
|
||||
end
|
||||
)
|
||||
(func $assembly/index/insertBlock (; 2 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/insertBlock (; 3 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -180,7 +188,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $4
|
||||
call $assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $1
|
||||
local.get $2
|
||||
i32.const 3
|
||||
@ -227,7 +235,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $3
|
||||
local.get $6
|
||||
i32.const 3
|
||||
@ -339,7 +347,7 @@
|
||||
i32.or
|
||||
i32.store offset=4
|
||||
)
|
||||
(func $assembly/index/addMemory (; 3 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(func $~lib/rt/tlsf/addMemory (; 4 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
local.get $2
|
||||
block (result i32)
|
||||
@ -401,9 +409,9 @@
|
||||
i32.store offset=1568
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
)
|
||||
(func $assembly/index/initialize (; 4 ;) (type $FUNCSIG$i) (result i32)
|
||||
(func $~lib/rt/tlsf/initializeRoot (; 5 ;) (type $FUNCSIG$v)
|
||||
(local $0 i32)
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
@ -495,10 +503,11 @@
|
||||
current_memory
|
||||
i32.const 16
|
||||
i32.shl
|
||||
call $assembly/index/addMemory
|
||||
call $~lib/rt/tlsf/addMemory
|
||||
local.get $0
|
||||
global.set $~lib/rt/tlsf/ROOT
|
||||
)
|
||||
(func $assembly/index/prepareSize (; 5 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/prepareSize (; 6 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(local $1 i32)
|
||||
local.get $0
|
||||
i32.const 1073741824
|
||||
@ -506,7 +515,7 @@
|
||||
if
|
||||
i32.const 0
|
||||
i32.const 24
|
||||
i32.const 466
|
||||
i32.const 436
|
||||
i32.const 29
|
||||
call $~lib/builtins/abort
|
||||
unreachable
|
||||
@ -524,7 +533,7 @@
|
||||
i32.gt_u
|
||||
select
|
||||
)
|
||||
(func $assembly/index/searchBlock (; 6 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/searchBlock (; 7 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(local $2 i32)
|
||||
local.get $0
|
||||
local.get $1
|
||||
@ -625,7 +634,7 @@
|
||||
end
|
||||
end
|
||||
)
|
||||
(func $assembly/index/growMemory (; 7 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/growMemory (; 8 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
@ -664,9 +673,9 @@
|
||||
current_memory
|
||||
i32.const 16
|
||||
i32.shl
|
||||
call $assembly/index/addMemory
|
||||
call $~lib/rt/tlsf/addMemory
|
||||
)
|
||||
(func $assembly/index/prepareBlock (; 8 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(func $~lib/rt/tlsf/prepareBlock (; 9 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
local.get $1
|
||||
@ -701,7 +710,7 @@
|
||||
i32.store
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
else
|
||||
local.get $1
|
||||
local.get $3
|
||||
@ -730,23 +739,23 @@
|
||||
i32.store
|
||||
end
|
||||
)
|
||||
(func $assembly/index/allocateBlock (; 9 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/allocateBlock (; 10 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $assembly/index/prepareSize
|
||||
call $~lib/rt/tlsf/prepareSize
|
||||
local.tee $3
|
||||
call $assembly/index/searchBlock
|
||||
call $~lib/rt/tlsf/searchBlock
|
||||
local.tee $2
|
||||
i32.eqz
|
||||
if
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $assembly/index/growMemory
|
||||
call $~lib/rt/tlsf/growMemory
|
||||
local.get $0
|
||||
local.get $3
|
||||
call $assembly/index/searchBlock
|
||||
call $~lib/rt/tlsf/searchBlock
|
||||
local.set $2
|
||||
end
|
||||
local.get $2
|
||||
@ -760,30 +769,29 @@
|
||||
i32.store offset=12
|
||||
local.get $0
|
||||
local.get $2
|
||||
call $assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $0
|
||||
local.get $2
|
||||
local.get $3
|
||||
call $assembly/index/prepareBlock
|
||||
call $~lib/rt/tlsf/prepareBlock
|
||||
local.get $2
|
||||
)
|
||||
(func $assembly/index/__mm_allocate (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(func $~lib/rt/index/__mm_allocate (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32)
|
||||
(local $1 i32)
|
||||
global.get $assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.tee $1
|
||||
i32.eqz
|
||||
if
|
||||
call $assembly/index/initialize
|
||||
local.tee $1
|
||||
global.set $assembly/index/ROOT
|
||||
if (result i32)
|
||||
local.get $1
|
||||
else
|
||||
call $~lib/rt/tlsf/initializeRoot
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
end
|
||||
local.get $1
|
||||
local.get $0
|
||||
call $assembly/index/allocateBlock
|
||||
call $~lib/rt/tlsf/allocateBlock
|
||||
i32.const 16
|
||||
i32.add
|
||||
)
|
||||
(func $~lib/memory/memory.copy (; 11 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(func $~lib/memory/memory.copy (; 12 ;) (type $FUNCSIG$viii) (param $0 i32) (param $1 i32) (param $2 i32)
|
||||
(local $3 i32)
|
||||
block $~lib/util/memory/memmove|inlined.0
|
||||
local.get $0
|
||||
@ -957,13 +965,13 @@
|
||||
end
|
||||
end
|
||||
)
|
||||
(func $assembly/index/reallocateBlock (; 12 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
|
||||
(func $~lib/rt/tlsf/reallocateBlock (; 13 ;) (type $FUNCSIG$iiii) (param $0 i32) (param $1 i32) (param $2 i32) (result i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
(local $6 i32)
|
||||
local.get $2
|
||||
call $assembly/index/prepareSize
|
||||
call $~lib/rt/tlsf/prepareSize
|
||||
local.tee $3
|
||||
local.get $1
|
||||
i32.load
|
||||
@ -975,7 +983,7 @@
|
||||
local.get $0
|
||||
local.get $1
|
||||
local.get $3
|
||||
call $assembly/index/prepareBlock
|
||||
call $~lib/rt/tlsf/prepareBlock
|
||||
local.get $1
|
||||
local.get $2
|
||||
i32.store offset=12
|
||||
@ -1011,7 +1019,7 @@
|
||||
if
|
||||
local.get $0
|
||||
local.get $6
|
||||
call $assembly/index/removeBlock
|
||||
call $~lib/rt/tlsf/removeBlock
|
||||
local.get $1
|
||||
local.get $4
|
||||
i32.const 3
|
||||
@ -1025,14 +1033,14 @@
|
||||
local.get $0
|
||||
local.get $1
|
||||
local.get $3
|
||||
call $assembly/index/prepareBlock
|
||||
call $~lib/rt/tlsf/prepareBlock
|
||||
local.get $1
|
||||
return
|
||||
end
|
||||
end
|
||||
local.get $0
|
||||
local.get $2
|
||||
call $assembly/index/allocateBlock
|
||||
call $~lib/rt/tlsf/allocateBlock
|
||||
local.tee $3
|
||||
local.get $1
|
||||
i32.load offset=4
|
||||
@ -1056,20 +1064,20 @@
|
||||
i32.store
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
local.get $3
|
||||
)
|
||||
(func $assembly/index/__mm_reallocate (; 13 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
global.get $assembly/index/ROOT
|
||||
(func $~lib/rt/index/__mm_reallocate (; 14 ;) (type $FUNCSIG$iii) (param $0 i32) (param $1 i32) (result i32)
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.get $0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.get $1
|
||||
call $assembly/index/reallocateBlock
|
||||
call $~lib/rt/tlsf/reallocateBlock
|
||||
i32.const 16
|
||||
i32.add
|
||||
)
|
||||
(func $assembly/index/freeBlock (; 14 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
(func $~lib/rt/tlsf/freeBlock (; 15 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
local.get $1
|
||||
local.get $1
|
||||
i32.load
|
||||
@ -1078,31 +1086,50 @@
|
||||
i32.store
|
||||
local.get $0
|
||||
local.get $1
|
||||
call $assembly/index/insertBlock
|
||||
call $~lib/rt/tlsf/insertBlock
|
||||
)
|
||||
(func $assembly/index/__mm_free (; 15 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
global.get $assembly/index/ROOT
|
||||
(func $~lib/rt/index/__mm_free (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.get $0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
call $assembly/index/freeBlock
|
||||
call $~lib/rt/tlsf/freeBlock
|
||||
)
|
||||
(func $assembly/index/decrement (; 16 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(func $~lib/rt/index/__gc_retain (; 17 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
i32.const 268435455
|
||||
i32.and
|
||||
i32.const 1
|
||||
i32.eq
|
||||
i32.eqz
|
||||
if
|
||||
local.get $0
|
||||
i32.load offset=8
|
||||
drop
|
||||
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
|
||||
unreachable
|
||||
)
|
||||
(func $assembly/index/markGray (; 17 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(func $~lib/rt/index/__gc_release (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
local.get $0
|
||||
if
|
||||
local.get $0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.tee $0
|
||||
i32.load offset=4
|
||||
i32.const 268435455
|
||||
i32.and
|
||||
i32.const 1
|
||||
i32.ne
|
||||
if
|
||||
local.get $0
|
||||
i32.load offset=8
|
||||
drop
|
||||
end
|
||||
unreachable
|
||||
end
|
||||
)
|
||||
(func $~lib/rt/pure/markGray (; 19 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(local $1 i32)
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
@ -1122,16 +1149,7 @@
|
||||
unreachable
|
||||
end
|
||||
)
|
||||
(func $assembly/index/scanBlack (; 18 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
local.get $0
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
i32.const -1879048193
|
||||
i32.and
|
||||
i32.store offset=4
|
||||
unreachable
|
||||
)
|
||||
(func $assembly/index/scan (; 19 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(func $~lib/rt/pure/scan (; 20 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(local $1 i32)
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
@ -1148,7 +1166,11 @@
|
||||
i32.gt_u
|
||||
if
|
||||
local.get $0
|
||||
call $assembly/index/scanBlack
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
i32.const -1879048193
|
||||
i32.and
|
||||
i32.store offset=4
|
||||
else
|
||||
local.get $0
|
||||
local.get $1
|
||||
@ -1157,11 +1179,11 @@
|
||||
i32.const 536870912
|
||||
i32.or
|
||||
i32.store offset=4
|
||||
unreachable
|
||||
end
|
||||
unreachable
|
||||
end
|
||||
)
|
||||
(func $assembly/index/collectWhite (; 20 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(func $~lib/rt/pure/collectWhite (; 21 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
(local $1 i32)
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
@ -1179,95 +1201,22 @@
|
||||
if
|
||||
unreachable
|
||||
end
|
||||
global.get $assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.get $0
|
||||
call $assembly/index/freeBlock
|
||||
call $~lib/rt/tlsf/freeBlock
|
||||
)
|
||||
(func $assembly/index/__rt_visit (; 21 ;) (type $FUNCSIG$vii) (param $0 i32) (param $1 i32)
|
||||
block $break|0
|
||||
block $case4|0
|
||||
block $case3|0
|
||||
block $case2|0
|
||||
block $case1|0
|
||||
block $case0|0
|
||||
local.get $1
|
||||
i32.const 1
|
||||
i32.sub
|
||||
br_table $case0|0 $case1|0 $case2|0 $case3|0 $case4|0 $break|0
|
||||
end
|
||||
local.get $0
|
||||
call $assembly/index/decrement
|
||||
br $break|0
|
||||
end
|
||||
local.get $0
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
i32.const 1
|
||||
i32.sub
|
||||
i32.store offset=4
|
||||
local.get $0
|
||||
call $assembly/index/markGray
|
||||
br $break|0
|
||||
end
|
||||
local.get $0
|
||||
call $assembly/index/scan
|
||||
br $break|0
|
||||
end
|
||||
local.get $0
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
local.tee $1
|
||||
i32.const 1
|
||||
i32.add
|
||||
i32.store offset=4
|
||||
local.get $1
|
||||
i32.const 1879048192
|
||||
i32.and
|
||||
if
|
||||
local.get $0
|
||||
call $assembly/index/scanBlack
|
||||
end
|
||||
br $break|0
|
||||
end
|
||||
local.get $0
|
||||
call $assembly/index/collectWhite
|
||||
end
|
||||
)
|
||||
(func $assembly/index/__gc_retain (; 22 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
local.get $0
|
||||
if
|
||||
local.get $0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.tee $0
|
||||
local.get $0
|
||||
i32.load offset=4
|
||||
i32.const 1
|
||||
i32.add
|
||||
i32.store offset=4
|
||||
end
|
||||
)
|
||||
(func $assembly/index/__gc_release (; 23 ;) (type $FUNCSIG$vi) (param $0 i32)
|
||||
local.get $0
|
||||
if
|
||||
local.get $0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
call $assembly/index/decrement
|
||||
end
|
||||
)
|
||||
(func $assembly/index/collectCycles (; 24 ;) (type $FUNCSIG$v)
|
||||
(func $~lib/rt/pure/collectCycles (; 22 ;) (type $FUNCSIG$v)
|
||||
(local $0 i32)
|
||||
(local $1 i32)
|
||||
(local $2 i32)
|
||||
(local $3 i32)
|
||||
(local $4 i32)
|
||||
(local $5 i32)
|
||||
global.get $assembly/index/ROOTS
|
||||
global.get $~lib/rt/pure/ROOTS
|
||||
local.tee $5
|
||||
local.tee $2
|
||||
local.set $3
|
||||
global.get $assembly/index/CUR
|
||||
global.get $~lib/rt/pure/CUR
|
||||
local.set $0
|
||||
loop $repeat|0
|
||||
block $break|0
|
||||
@ -1293,7 +1242,7 @@
|
||||
select
|
||||
if
|
||||
local.get $4
|
||||
call $assembly/index/markGray
|
||||
call $~lib/rt/pure/markGray
|
||||
local.get $2
|
||||
local.get $4
|
||||
i32.store
|
||||
@ -1312,9 +1261,9 @@
|
||||
i32.and
|
||||
select
|
||||
if
|
||||
global.get $assembly/index/ROOT
|
||||
global.get $~lib/rt/tlsf/ROOT
|
||||
local.get $4
|
||||
call $assembly/index/freeBlock
|
||||
call $~lib/rt/tlsf/freeBlock
|
||||
else
|
||||
local.get $4
|
||||
local.get $1
|
||||
@ -1331,7 +1280,7 @@
|
||||
end
|
||||
end
|
||||
local.get $2
|
||||
global.set $assembly/index/CUR
|
||||
global.set $~lib/rt/pure/CUR
|
||||
local.get $5
|
||||
local.set $0
|
||||
loop $repeat|1
|
||||
@ -1342,7 +1291,7 @@
|
||||
br_if $break|1
|
||||
local.get $0
|
||||
i32.load
|
||||
call $assembly/index/scan
|
||||
call $~lib/rt/pure/scan
|
||||
local.get $0
|
||||
i32.const 4
|
||||
i32.add
|
||||
@ -1367,7 +1316,7 @@
|
||||
i32.and
|
||||
i32.store offset=4
|
||||
local.get $1
|
||||
call $assembly/index/collectWhite
|
||||
call $~lib/rt/pure/collectWhite
|
||||
local.get $0
|
||||
i32.const 4
|
||||
i32.add
|
||||
@ -1376,9 +1325,12 @@
|
||||
end
|
||||
end
|
||||
local.get $5
|
||||
global.set $assembly/index/CUR
|
||||
global.set $~lib/rt/pure/CUR
|
||||
)
|
||||
(func $null (; 25 ;) (type $FUNCSIG$v)
|
||||
(func $~lib/rt/index/__gc_collect (; 23 ;) (type $FUNCSIG$v)
|
||||
call $~lib/rt/pure/collectCycles
|
||||
)
|
||||
(func $start (; 24 ;) (type $FUNCSIG$v)
|
||||
nop
|
||||
)
|
||||
)
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user