Initial new rt integration

This commit is contained in:
dcode
2019-05-12 13:50:28 +02:00
parent dd2bdd0383
commit ba1a0c2369
52 changed files with 1066 additions and 2985 deletions

View File

@ -1,21 +0,0 @@
Memory manager interface
========================
A memory manager for AssemblyScript must implement the following common and may implement any number of optional interfaces:
Common
------
* **__mem_allocate**(size: `usize`): `usize`<br />
Dynamically allocates a chunk of memory of at least the specified size and returns its address.
Alignment must be guaranteed to be at least 8 bytes, but there are considerations to increase
alignment to 16 bytes to fit SIMD v128 values.
* **__mem_free**(ref: `usize`): `void`<br />
Frees a dynamically allocated chunk of memory by its address.
Optional
--------
* **__mem_reset**(ref: `usize`, parentRef: `usize`): `void`<br />
Resets dynamic memory to its initial state. Used by the arena allocator.

View File

@ -1,41 +0,0 @@
import { HEAP_BASE, memory } from "../memory";
import { AL_MASK, MAX_SIZE_32 } from "../util/allocator";
// @ts-ignore: decorator
@lazy
var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@lazy
var offset: usize = startOffset;
// @ts-ignore: decorator
@unsafe @global
function __mem_allocate(size: usize): usize {
if (size > MAX_SIZE_32) unreachable();
var ptr = offset;
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
var pagesBefore = memory.size();
if (newPtr > <usize>pagesBefore << 16) {
let pagesNeeded = ((newPtr - ptr + 0xffff) & ~0xffff) >>> 16;
let pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) {
unreachable(); // out of memory
}
}
}
offset = newPtr;
return ptr;
}
// @ts-ignore: decorator
@unsafe @global
function __mem_free(ptr: usize): void {
}
// @ts-ignore: decorator
@unsafe @global
function __mem_reset(): void {
offset = startOffset;
}

View File

@ -1,19 +0,0 @@
// @ts-ignore: decorator
@unsafe
declare function _malloc(size: usize): usize;
// @ts-ignore: decorator
@unsafe
declare function _free(ptr: usize): void;
// @ts-ignore: decorator
@unsafe @global
function __mem_allocate(size: usize): usize {
return _malloc(size);
}
// @ts-ignore: decorator
@unsafe @global
function __mem_free(ptr: usize): void {
_free(ptr);
}

View File

@ -1,3 +0,0 @@
declare function __mem_allocate(size: usize): usize;
declare function __mem_free(ref: usize): void;
declare function __mem_reset(): void;

View File

@ -1,19 +0,0 @@
// @ts-ignore: decorator
@unsafe
declare function malloc(size: usize): usize;
// @ts-ignore: decorator
@unsafe
declare function free(ptr: usize): void;
// @ts-ignore: decorator
@unsafe @global
function __mem_allocate(size: usize): usize {
return malloc(size);
}
// @ts-ignore: decorator
@unsafe @global
function __mem_free(ptr: usize): void {
free(ptr);
}

View File

@ -1,529 +0,0 @@
// Two-Level Segregate Fit Memory Allocator.
//
// A general purpose dynamic memory allocator specifically designed to meet real-time requirements.
// Always aligns to 8 bytes.
// ╒══════════════ 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
import { AL_BITS, AL_SIZE, AL_MASK } from "../util/allocator";
import { HEAP_BASE, memory } from "../memory";
// @ts-ignore: decorator
@inline
const SL_BITS: u32 = 5;
// @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 = (sizeof<usize>() == sizeof<u32>()
? 30 // ^= up to 1GB per block
: 32 // ^= up to 4GB per block
) - SB_BITS;
// ╒════════════════ Block structure 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┤
// │ size │L│F│ ◄─┐ info
// ╞═══════════════════════════════════════════════════════════╧═╧═╡ │ ┐
// │ if free: ◄ prev │ ◄─┤ usize
// ├───────────────────────────────────────────────────────────────┤ │
// │ if free: next ► │ ◄─┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ ... unused free space >= 0 ... │ │ = 0
// ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │
// │ if free: jump ▲ │ ◄─┘
// └───────────────────────────────────────────────────────────────┘ MIN SIZE ┘
// F: FREE, L: LEFT_FREE
/** Tag indicating that this block is free. */
// @ts-ignore: decorator
@inline
const FREE: usize = 1 << 0;
/** Tag indicating that this block's left block is free. */
// @ts-ignore: decorator
@inline
const LEFT_FREE: usize = 1 << 1;
/** Mask to obtain all tags. */
// @ts-ignore: decorator
@inline
const TAGS: usize = FREE | LEFT_FREE;
/** Block structure. */
@unmanaged class Block {
/** Info field holding this block's size and tags. */
info: usize;
/** Class id. */ // TODO
// classId: u32; //
/** Size of the payload. */ //
// payloadSize: u32; //
/** Reference count. */ //
// refCount: u32; //
/** Size of the always present header fields. User data starts here. */
@inline
static readonly HEADER_SIZE: usize = (offsetof<Block>("prev") + AL_MASK) & ~AL_MASK;
/** Previous free block, if any. Only valid if free. */
prev: Block | null;
/** Next free block, if any. Only valid if free. */
next: Block | null;
/** Minimum size of a block, excluding {@link Block#info}. */
@inline
static readonly MIN_SIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK;// prev + next + jump
/** Maximum size of a used block, excluding {@link Block#info}. */
@inline
static readonly MAX_SIZE: usize = 1 << (FL_BITS + SB_BITS);
/** Gets this block's left (free) block in memory. */
get left(): Block {
assert(this.info & LEFT_FREE); // left must be free or it doesn't contain 'jump'
return assert(
load<Block>(changetype<usize>(this) - sizeof<usize>())
); // can't be null
}
/** Gets this block's right block in memory. */
get right(): Block {
assert(this.info & ~TAGS); // can't skip beyond the tail block (the only valid empty block)
return assert(
changetype<Block>(
changetype<usize>(this) + Block.HEADER_SIZE + (this.info & ~TAGS)
)
); // can't be null
}
}
// ╒════════════════ Root structure 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] P │ ◄─┘ │
// ╞═══════════════════════════════════════════════════════════════╡ usize
// │ head[0] │ ◄────┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ ... │ ◄────┤
// ├───────────────────────────────────────────────────────────────┤ │
// │ head[736] │ ◄────┤
// ╞═══════════════════════════════════════════════════════════════╡ │
// │ tailRef │ ◄────┘
// └───────────────────────────────────────────────────────────────┘ SIZE ┘
// S: Small blocks map, P: Possibly padded if 64-bit
// assert((1 << SL_BITS) <= 32); // second level must fit into 32 bits
/** Root structure. */
@unmanaged class Root {
/** First level bitmap. */
flMap: usize = 0;
/** Start offset of second level maps. */
@inline
private static readonly SL_START: usize = sizeof<usize>();
// Using *one* SL map per *FL bit*
/** Gets the second level map for the specified first level. */
getSLMap(fl: usize): u32 {
assert(fl < FL_BITS); // fl out of range
return load<u32>(changetype<usize>(this) + fl * 4, Root.SL_START);
}
/** Sets the second level map for the specified first level. */
setSLMap(fl: usize, value: u32): void {
assert(fl < FL_BITS); // fl out of range
store<u32>(changetype<usize>(this) + fl * 4, value, Root.SL_START);
}
/** End offset of second level maps. */
@inline
private static readonly SL_END: usize = Root.SL_START + FL_BITS * 4;
// Using *number bits per SL* heads per *FL bit*
/** Start offset of FL/SL heads. */
@inline
private static readonly HL_START: usize = (Root.SL_END + AL_MASK) & ~AL_MASK;
/** Gets the head of the specified first and second level index. */
getHead(fl: usize, sl: u32): Block | null {
assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
return changetype<Block>(load<usize>(
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>()
, Root.HL_START));
}
/** Sets the head of the specified first and second level index. */
setHead(fl: usize, sl: u32, value: Block | null): void {
assert(fl < FL_BITS && sl < SL_SIZE); // fl/sl out of range
store<usize>(
changetype<usize>(this) + (fl * SL_SIZE + <usize>sl) * sizeof<usize>(),
changetype<usize>(value),
Root.HL_START
);
}
/** End offset of FL/SL heads. */
@inline
private static readonly HL_END: usize = Root.HL_START + FL_BITS * SL_SIZE * sizeof<usize>();
get tailRef(): usize { return load<usize>(changetype<usize>(this), Root.HL_END); }
set tailRef(value: usize) { store<usize>(changetype<usize>(this), value, Root.HL_END); }
/** Total size of the {@link Root} structure. */
@inline
static readonly SIZE: usize = Root.HL_END + sizeof<usize>();
/** Inserts a previously used block back into the free list. */
insert(block: Block): void {
// check as much as possible here to prevent invalid free blocks
assert(block); // cannot be null
var blockInfo = block.info;
assert(blockInfo & FREE); // must be free
var right = block.right; // asserts != null
var rightInfo = right.info;
// merge with right block if also free
if (rightInfo & FREE) {
this.remove(right);
block.info = (blockInfo += Block.HEADER_SIZE + (rightInfo & ~TAGS));
right = block.right;
rightInfo = right.info;
// jump is set below
}
// merge with left block if also free
if (blockInfo & LEFT_FREE) {
let left = block.left; // asserts != null
let leftInfo = left.info;
assert(leftInfo & FREE); // must be free according to right tags
this.remove(left);
left.info = (leftInfo += Block.HEADER_SIZE + (blockInfo & ~TAGS));
block = left;
blockInfo = leftInfo;
// jump is set below
}
right.info = rightInfo | LEFT_FREE;
this.setJump(block, right);
// right is no longer used now, hence rightInfo is not synced
var size = blockInfo & ~TAGS;
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
// mapping_insert
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
fl = fls<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
// perform insertion
var head = this.getHead(fl, sl);
block.prev = null;
block.next = head;
if (head) head.prev = block;
this.setHead(fl, sl, block);
// update first and second level maps
this.flMap |= (1 << fl);
this.setSLMap(fl, this.getSLMap(fl) | (1 << sl));
}
/**
* Removes a free block from FL/SL maps. Does not alter left/jump because it
* is likely that splitting is performed afterwards, invalidating any changes
* again.
*/
private remove(block: Block): void {
var blockInfo = block.info;
assert(blockInfo & FREE); // must be free
var size = blockInfo & ~TAGS;
assert(size >= Block.MIN_SIZE && size < Block.MAX_SIZE); // must be valid
// mapping_insert
var fl: usize, sl: u32;
if (size < SB_SIZE) {
fl = 0;
sl = <u32>(size / AL_SIZE);
} else {
fl = fls<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
}
// 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 == this.getHead(fl, sl)) {
this.setHead(fl, sl, next);
// clear second level map if head is empty now
if (!next) {
let slMap = this.getSLMap(fl);
this.setSLMap(fl, slMap &= ~(1 << sl));
// clear first level map if second level is empty now
if (!slMap) this.flMap &= ~(1 << fl);
}
}
}
/** Searches for a free block of at least the specified size. */
search(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 {
// (*) size += (1 << (fls<usize>(size) - SL_BITS)) - 1;
fl = fls<usize>(size);
sl = <u32>((size >> (fl - SL_BITS)) ^ (1 << SL_BITS));
fl -= SB_BITS - 1;
// (*) instead of rounding up, use next second level list for better fit
if (sl < SL_SIZE - 1) ++sl;
else ++fl, sl = 0;
}
// search second level
var slMap = this.getSLMap(fl) & (~0 << sl);
var head: Block | null;
if (!slMap) {
// search next larger first level
let flMap = this.flMap & (~0 << (fl + 1));
if (!flMap) {
head = null;
} else {
fl = ffs<usize>(flMap);
slMap = assert(this.getSLMap(fl)); // can't be zero if fl points here
head = this.getHead(fl, ffs<u32>(slMap));
}
} else {
head = this.getHead(fl, ffs<u32>(slMap));
}
return head;
}
/** Links a free left with its right block in memory. */
private setJump(left: Block, right: Block): void {
assert(
(left.info & FREE) != 0 && // must be free to contain 'jump'
left.right == right && // right block must match
(right.info & LEFT_FREE) != 0 // free status must match
);
store<Block>(
changetype<usize>(right) - sizeof<usize>()
, left); // last word in left block's (free) data region
}
/**
* Uses the specified free block, removing it from internal maps and
* splitting it if possible, and returns its data pointer.
*/
use(block: Block, size: usize): usize {
// size was already asserted by caller
var blockInfo = block.info;
assert(
(blockInfo & FREE) != 0 && // must be free
!(size & AL_MASK) // size must be aligned so the new block is
);
this.remove(block);
// split if the block can hold another MIN_SIZE block
var remaining = (blockInfo & ~TAGS) - size;
if (remaining >= Block.HEADER_SIZE + Block.MIN_SIZE) {
block.info = size | (blockInfo & LEFT_FREE); // also discards FREE
let spare = changetype<Block>(
changetype<usize>(block) + Block.HEADER_SIZE + size
);
spare.info = (remaining - Block.HEADER_SIZE) | FREE; // not LEFT_FREE
this.insert(spare); // also sets jump
// otherwise tag block as no longer FREE and right as no longer LEFT_FREE
} else {
block.info = blockInfo & ~FREE;
let right = block.right; // asserts != null
right.info &= ~LEFT_FREE;
}
return changetype<usize>(block) + Block.HEADER_SIZE;
}
/** Adds more memory to the pool. */
addMemory(start: usize, end: usize): bool {
assert(
start <= end && // must be valid
!(start & AL_MASK) && // must be aligned
!(end & AL_MASK) // must be aligned
);
var tailRef = this.tailRef;
var tailInfo: usize = 0;
if (tailRef) {
assert(start >= tailRef + Block.HEADER_SIZE); // starts after tail
// merge with current tail if adjacent
if (start - Block.HEADER_SIZE == tailRef) {
start -= Block.HEADER_SIZE;
tailInfo = changetype<Block>(tailRef).info;
}
} else {
assert(start >= changetype<usize>(this) + Root.SIZE); // starts after root
}
// check if size is large enough for a free block and the tail block
var size = end - start;
if (size < Block.HEADER_SIZE + Block.MIN_SIZE + Block.HEADER_SIZE) {
return false;
}
// left size is total minus its own and the zero-length tail's header
var leftSize = size - 2 * Block.HEADER_SIZE;
var left = changetype<Block>(start);
left.info = leftSize | FREE | (tailInfo & LEFT_FREE);
left.prev = null;
left.next = null;
// tail is a zero-length used block
var tail = changetype<Block>(start + size - Block.HEADER_SIZE);
tail.info = 0 | LEFT_FREE;
this.tailRef = changetype<usize>(tail);
this.insert(left); // also merges with free left before tail / sets jump
return true;
}
}
/** Determines the first (LSB to MSB) set bit's index of a word. */
function ffs<T extends number>(word: T): T {
assert(word != 0); // word cannot be 0
return ctz<T>(word); // differs from ffs only for 0
}
/** Determines the last (LSB to MSB) set bit's index of a word. */
function fls<T extends number>(word: T): T {
assert(word != 0); // word cannot be 0
// @ts-ignore: type
const inv: T = (sizeof<T>() << 3) - 1;
// @ts-ignore: type
return inv - clz<T>(word);
}
/** Reference to the initialized {@link Root} structure, once initialized. */
// @ts-ignore: decorator
@lazy
var ROOT: Root = changetype<Root>(0);
/** Allocates a chunk of memory. */
// @ts-ignore: decorator
@unsafe @global
function __mem_allocate(size: usize): usize {
// initialize if necessary
var root = ROOT;
if (!root) {
let rootOffset = (HEAP_BASE + AL_MASK) & ~AL_MASK;
let pagesBefore = memory.size();
let pagesNeeded = <i32>((((rootOffset + Root.SIZE) + 0xffff) & ~0xffff) >>> 16);
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
ROOT = root = changetype<Root>(rootOffset);
root.tailRef = 0;
root.flMap = 0;
for (let fl: usize = 0; fl < FL_BITS; ++fl) {
root.setSLMap(fl, 0);
for (let sl: u32 = 0; sl < SL_SIZE; ++sl) {
root.setHead(fl, sl, null);
}
}
root.addMemory((rootOffset + Root.SIZE + AL_MASK) & ~AL_MASK, memory.size() << 16);
}
// search for a suitable block
if (size > Block.MAX_SIZE) unreachable();
// 32-bit MAX_SIZE is 1 << 30 and itself aligned, hence the following can't overflow MAX_SIZE
size = max<usize>((size + AL_MASK) & ~AL_MASK, Block.MIN_SIZE);
var block = root.search(size);
if (!block) {
// request more memory
let pagesBefore = memory.size();
let pagesNeeded = <i32>(((size + 0xffff) & ~0xffff) >>> 16);
let pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) {
unreachable(); // out of memory
}
}
let pagesAfter = memory.size();
root.addMemory(<usize>pagesBefore << 16, <usize>pagesAfter << 16);
block = assert(root.search(size)); // must be found now
}
assert((block.info & ~TAGS) >= size); // must fit
return root.use(<Block>block, size);
}
/** Frees the chunk of memory at the specified address. */
// @ts-ignore: decorator
@unsafe @global
function __mem_free(data: usize): void {
if (data) {
assert(!(data & AL_MASK)); // must be aligned
let root = ROOT;
if (root) {
let block = changetype<Block>(data - Block.HEADER_SIZE);
let blockInfo = block.info;
assert(!(blockInfo & FREE)); // must be used
block.info = blockInfo | FREE;
root.insert(changetype<Block>(data - Block.HEADER_SIZE));
}
}
}

View File

@ -1,22 +1,21 @@
/// <reference path="./collector/index.d.ts" />
/// <reference path="./rt/index.d.ts" />
import { MAX_BYTELENGTH, allocate, reallocate, discard, register, NEWARRAY } from "./util/runtime";
import { BLOCK_MAXSIZE } from "./rt/common";
import { COMPARATOR, SORT } from "./util/sort";
import { __runtime_id, __gc_mark_members } from "./runtime";
import { ArrayBuffer, ArrayBufferView } from "./arraybuffer";
import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number";
import { isArray as builtin_isArray } from "./builtins";
import { idof, isArray as builtin_isArray } from "./builtins";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error";
/** Ensures that the given array has _at least_ the specified capacity. */
function ensureCapacity(array: ArrayBufferView, minCapacity: i32, alignLog2: u32): void {
if (<u32>minCapacity > <u32>array.dataLength >>> alignLog2) {
if (<u32>minCapacity > <u32>(MAX_BYTELENGTH >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH);
if (<u32>minCapacity > <u32>(BLOCK_MAXSIZE >>> alignLog2)) throw new RangeError(E_INVALIDLENGTH);
let oldData = array.data;
let newByteLength = minCapacity << alignLog2;
let newData = reallocate(changetype<usize>(oldData), <usize>newByteLength); // registers on move
let newData = __realloc(changetype<usize>(oldData), <usize>newByteLength); // registers on move
if (newData !== changetype<usize>(oldData)) {
array.data = changetype<ArrayBuffer>(newData); // links
array.data = changetype<ArrayBuffer>(newData); // retains
array.dataStart = newData;
}
array.dataLength = newByteLength;
@ -41,11 +40,11 @@ export class Array<T> extends ArrayBufferView {
}
static create<T>(capacity: i32 = 0): Array<T> {
if (<u32>capacity > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
var array = NEWARRAY<T>(capacity);
array.length_ = 0; // safe even if T is a non-nullable reference
memory.fill(array.dataStart, 0, array.dataLength);
return array;
if (<u32>capacity > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
var array = __allocArray(capacity, alignof<T>(), idof<T[]>());
changetype<Array<T>>(array).length_ = 0; // safe even if T is a non-nullable reference
memory.fill(changetype<ArrayBufferView>(array).dataStart, 0, changetype<ArrayBufferView>(array).dataLength);
return changetype<Array<T>>(array); // retains
}
constructor(length: i32 = 0) {
@ -119,27 +118,7 @@ export class Array<T> extends ArrayBufferView {
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
if (isManaged<T>()) {
let offset = this.dataStart + (<usize>index << alignof<T>());
let oldValue = load<T>(offset);
if (value !== oldValue) {
store<T>(offset, value);
if (isNullable<T>()) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (isDefined(__ref_retain)) {
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
if (value !== null) __ref_retain(changetype<usize>(value));
} else assert(false);
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
__ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (__ref_retain) {
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
__ref_retain(changetype<usize>(value));
} else assert(false);
}
}
store<usize>(offset, __retainRelease(changetype<usize>(value), load<usize>(offset)));
} else {
store<T>(this.dataStart + (<usize>index << alignof<T>()), value);
}
@ -201,27 +180,7 @@ export class Array<T> extends ArrayBufferView {
ensureCapacity(this, newLength, alignof<T>());
if (isManaged<T>()) {
let offset = this.dataStart + (<usize>length << alignof<T>());
let oldValue = load<T>(offset);
if (oldValue !== value) {
store<T>(offset, value);
if (isNullable<T>()) {
if (isDefined(__ref_link)) {
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (__ref_retain) {
if (oldValue !== null) __ref_retain(changetype<usize>(value));
if (value !== null) __ref_release(changetype<usize>(oldValue));
} else assert(false);
} else {
if (isDefined(__ref_link)) {
__ref_link(changetype<usize>(value), changetype<usize>(this));
if (isDefined(__ref_unlink)) if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (__ref_retain) {
__ref_retain(changetype<usize>(value));
if (oldValue !== null) __ref_release(changetype<usize>(oldValue));
} else assert(false);
}
}
store<usize>(offset, __retainRelease(changetype<usize>(value), load<usize>(offset)));
} else {
store<T>(this.dataStart + (<usize>length << alignof<T>()), value);
}
@ -233,50 +192,28 @@ export class Array<T> extends ArrayBufferView {
var thisLen = this.length_;
var otherLen = select(0, other.length_, other === null);
var outLen = thisLen + otherLen;
if (<u32>outLen > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new Error(E_INVALIDLENGTH);
var out = NEWARRAY<T>(outLen);
var outStart = out.dataStart;
if (<u32>outLen > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new Error(E_INVALIDLENGTH);
var out = __allocArray(outLen, alignof<T>(), idof<Array<T>>());
var outStart = changetype<ArrayBufferView>(out).dataStart;
var thisSize = <usize>thisLen << alignof<T>();
if (isManaged<T>()) {
let thisStart = this.dataStart;
for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) {
let ref = load<usize>(thisStart + offset);
store<usize>(outStart + offset, ref);
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
store<usize>(outStart + offset, __retain(ref));
}
outStart += thisSize;
let otherStart = other.dataStart;
let otherSize = <usize>otherLen << alignof<T>();
for (let offset: usize = 0; offset < otherSize; offset += sizeof<T>()) {
let ref = load<usize>(otherStart + offset);
store<usize>(outStart + offset, ref);
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
store<usize>(outStart + offset, __retain(ref));
}
} else {
memory.copy(outStart, this.dataStart, thisSize);
memory.copy(outStart + thisSize, other.dataStart, <usize>otherLen << alignof<T>());
}
return out;
return changetype<Array<T>>(out);
}
copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this {
@ -322,38 +259,27 @@ export class Array<T> extends ArrayBufferView {
map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> {
var length = this.length_;
var out = NEWARRAY<U>(length);
var outStart = out.dataStart;
var out = __allocArray(length, alignof<U>(), idof<Array<U>>());
var outStart = changetype<ArrayBufferView>(out).dataStart;
for (let index = 0; index < min(length, this.length_); ++index) {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
let result = callbackfn(load<T>(this.dataStart + (<usize>index << alignof<T>())), index, this); // retains
if (isManaged<U>()) {
let ref = changetype<usize>(callbackfn(value, index, this));
store<usize>(outStart + (<usize>index << alignof<U>()), ref);
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(out));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(out));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
store<usize>(outStart + (<usize>index << alignof<U>()), __retain(changetype<usize>(result)));
} else {
store<U>(outStart + (<usize>index << alignof<U>()), callbackfn(value, index, this));
store<U>(outStart + (<usize>index << alignof<U>()), result);
}
// releases result
}
return out;
return changetype<Array<U>>(out); // retains
}
filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> {
var result = NEWARRAY<T>(0);
var result = __allocArray(0, alignof<T>(), idof<Array<T>>());
for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
if (callbackfn(value, index, this)) result.push(value);
if (callbackfn(value, index, this)) changetype<Array<T>>(result).push(value);
}
return result;
return changetype<Array<T>>(result); // retains
}
reduce<U>(
@ -413,19 +339,10 @@ export class Array<T> extends ArrayBufferView {
dataStart,
<usize>(newLength - 1) << alignof<T>()
);
store<T>(dataStart, element);
if (isManaged<T>()) {
if (isNullable<T>()) {
if (element !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(element), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(element));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(element), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(element));
else assert(false);
}
store<usize>(dataStart, __retain(changetype<usize>(element)));
} else {
store<T>(dataStart, element);
}
this.length_ = newLength;
return newLength;
@ -436,61 +353,41 @@ export class Array<T> extends ArrayBufferView {
begin = begin < 0 ? max(begin + length, 0) : min(begin, length);
end = end < 0 ? max(end + length, 0) : min(end , length);
length = max(end - begin, 0);
var slice = NEWARRAY<T>(length);
var sliceBase = slice.dataStart;
var slice = __allocArray(length, alignof<T>(), idof<Array<T>>());
var sliceBase = changetype<ArrayBufferView>(slice).dataStart;
var thisBase = this.dataStart + (<usize>begin << alignof<T>());
if (isManaged<T>()) {
let off = <usize>0;
let end = <usize>length << alignof<usize>();
while (off < end) {
let ref = load<usize>(thisBase + off);
store<usize>(sliceBase + off, ref);
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(slice));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(slice));
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
store<usize>(sliceBase + off, __retain(ref));
off += sizeof<usize>();
}
} else {
memory.copy(sliceBase, thisBase, length << alignof<T>());
}
return slice;
return changetype<Array<T>>(slice); // retains
}
splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array<T> {
var length = this.length_;
start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length);
deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0);
var result = NEWARRAY<T>(deleteCount);
var resultStart = result.dataStart;
var result = __allocArray(deleteCount, alignof<T>(), idof<Array<T>>());
var resultStart = changetype<ArrayBufferView>(result).dataStart;
var thisStart = this.dataStart;
var thisBase = thisStart + (<usize>start << alignof<T>());
if (isManaged<T>()) {
for (let i = 0; i < deleteCount; ++i) {
let ref = load<usize>(thisBase + (<usize>i << alignof<T>()));
store<usize>(resultStart + (<usize>i << alignof<T>()), ref);
if (isDefined(__ref_link)) {
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(this));
__ref_link(ref, changetype<usize>(result));
}
} else {
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(this));
__ref_link(ref, changetype<usize>(result));
}
}
store<usize>(resultStart + (<usize>i << alignof<T>()),
load<usize>(thisBase + (<usize>i << alignof<T>()))
);
// element is moved, so refcount doesn't change
}
} else {
memory.copy(
result.dataStart,
resultStart,
thisBase,
<usize>deleteCount << alignof<T>()
);
@ -504,7 +401,7 @@ export class Array<T> extends ArrayBufferView {
);
}
this.length_ = length - deleteCount;
return result;
return changetype<Array<T>>(result); // retains
}
reverse(): Array<T> {
@ -563,7 +460,7 @@ export class Array<T> extends ArrayBufferView {
var sepLen = separator.length;
var valueLen = 5; // max possible length of element len("false")
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1);
var result = __alloc(estLen << 1, idof<string>());
var offset = 0;
var value: bool;
for (let i = 0; i < lastIndex; ++i) {
@ -594,11 +491,9 @@ export class Array<T> extends ArrayBufferView {
offset += valueLen;
if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset);
discard(result);
return trimmed; // registered in .substring
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
}
return changetype<string>(register(result, __runtime_id<string>()));
return changetype<string>(result); // retains
}
private join_int(separator: string = ","): string {
@ -606,12 +501,12 @@ export class Array<T> extends ArrayBufferView {
if (lastIndex < 0) return "";
var dataStart = this.dataStart;
// @ts-ignore: type
if (!lastIndex) return changetype<string>(itoa<T>(load<T>(dataStart)));
if (!lastIndex) return changetype<string>(itoa<T>(load<T>(dataStart))); // retains
var sepLen = separator.length;
const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + i32(isSigned<T>());
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1);
var result = __alloc(estLen << 1, idof<string>());
var offset = 0;
var value: T;
for (let i = 0; i < lastIndex; ++i) {
@ -631,11 +526,9 @@ export class Array<T> extends ArrayBufferView {
// @ts-ignore: type
offset += itoa_stream<T>(result, offset, value);
if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset);
discard(result);
return trimmed; // registered in .substring
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
}
return changetype<string>(register(result, __runtime_id<string>()));
return changetype<string>(result); // retains
}
private join_flt(separator: string = ","): string {
@ -646,13 +539,13 @@ export class Array<T> extends ArrayBufferView {
return changetype<string>(dtoa(
// @ts-ignore: type
load<T>(dataStart))
);
); // retains
}
const valueLen = MAX_DOUBLE_LENGTH;
var sepLen = separator.length;
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1);
var result = __alloc(estLen << 1, idof<string>());
var offset = 0;
var value: T;
for (let i = 0; i < lastIndex; ++i) {
@ -676,11 +569,9 @@ export class Array<T> extends ArrayBufferView {
value
);
if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset);
discard(result);
return trimmed; // registered in .substring
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
}
return changetype<string>(register(result, __runtime_id<string>()));
return changetype<string>(result); // retains
}
private join_str(separator: string = ","): string {
@ -697,7 +588,7 @@ export class Array<T> extends ArrayBufferView {
if (value !== null) estLen += value.length;
}
var offset = 0;
var result = allocate((estLen + sepLen * lastIndex) << 1);
var result = __alloc((estLen + sepLen * lastIndex) << 1, idof<string>());
for (let i = 0; i < lastIndex; ++i) {
value = load<string>(dataStart + (<usize>i << alignof<T>()));
if (value !== null) {
@ -726,7 +617,7 @@ export class Array<T> extends ArrayBufferView {
<usize>changetype<string>(value).length << 1
);
}
return changetype<string>(register(result, __runtime_id<string>()));
return changetype<string>(result); // retains
}
private join_arr(separator: string = ","): string {
@ -763,7 +654,7 @@ export class Array<T> extends ArrayBufferView {
const valueLen = 15; // max possible length of element len("[object Object]")
var sepLen = separator.length;
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = allocate(estLen << 1);
var result = __alloc(estLen << 1, idof<string>());
var offset = 0;
var value: T;
for (let i = 0; i < lastIndex; ++i) {
@ -794,11 +685,9 @@ export class Array<T> extends ArrayBufferView {
offset += valueLen;
}
if (estLen > offset) {
let out = changetype<string>(result).substring(0, offset);
discard(result);
return out; // registered in .substring
return changetype<string>(result).substring(0, offset); // retains/releases result, retains return
}
return changetype<string>(register(result, __runtime_id<string>()));
return changetype<string>(result); // retains
}
toString(): string {
@ -807,8 +696,8 @@ export class Array<T> extends ArrayBufferView {
// GC integration
@unsafe private __traverse(): void {
__ref_mark(changetype<usize>(this.data));
@unsafe private __traverse(cookie: u32): void {
__visit(changetype<usize>(this.data), cookie);
if (isManaged<T>()) {
let cur = this.dataStart;
let end = cur + <usize>this.dataLength;
@ -816,12 +705,12 @@ export class Array<T> extends ArrayBufferView {
let val = load<usize>(cur);
if (isNullable<T>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<T>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<T>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
cur += sizeof<usize>();
}

View File

@ -1,5 +1,7 @@
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime";
import { __runtime_id } from "./runtime";
/// <reference path="./rt/index.d.ts" />
import { BLOCK, BLOCK_MAXSIZE, BLOCK_OVERHEAD } from "./rt/common";
import { idof } from "./builtins";
import { E_INVALIDLENGTH } from "./util/error";
export abstract class ArrayBufferView {
@ -9,9 +11,9 @@ export abstract class ArrayBufferView {
@unsafe dataLength: u32;
protected constructor(length: i32, alignLog2: i32) {
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignLog2) throw new RangeError(E_INVALIDLENGTH);
if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignLog2) throw new RangeError(E_INVALIDLENGTH);
var buffer = new ArrayBuffer(length = length << alignLog2);
this.data = buffer;
this.data = buffer; // retains
this.dataStart = changetype<usize>(buffer);
this.dataLength = length;
}
@ -51,24 +53,24 @@ export abstract class ArrayBufferView {
}
constructor(length: i32) {
if (<u32>length > <u32>MAX_BYTELENGTH) throw new RangeError(E_INVALIDLENGTH);
var buffer = allocate(<usize>length);
memory.fill(changetype<usize>(buffer), 0, <usize>length);
return changetype<ArrayBuffer>(register(buffer, __runtime_id<ArrayBuffer>()));
if (<u32>length > <u32>BLOCK_MAXSIZE) throw new RangeError(E_INVALIDLENGTH);
var buffer = __alloc(<usize>length, idof<ArrayBuffer>());
memory.fill(buffer, 0, <usize>length);
return changetype<ArrayBuffer>(buffer); // retains
}
get byteLength(): i32 {
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize;
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize;
}
slice(begin: i32 = 0, end: i32 = MAX_BYTELENGTH): ArrayBuffer {
slice(begin: i32 = 0, end: i32 = BLOCK_MAXSIZE): ArrayBuffer {
var length = this.byteLength;
begin = begin < 0 ? max(length + begin, 0) : min(begin, length);
end = end < 0 ? max(length + end , 0) : min(end , length);
var outSize = <usize>max(end - begin, 0);
var out = allocate(outSize);
var out = __alloc(outSize, idof<ArrayBuffer>());
memory.copy(out, changetype<usize>(this) + <usize>begin, outSize);
return changetype<ArrayBuffer>(register(out, __runtime_id<ArrayBuffer>()));
return changetype<ArrayBuffer>(out); // retains
}
toString(): string {

View File

@ -1740,3 +1740,23 @@ declare function trace(
a3?: f64,
a4?: f64
): void;
// @ts-ignore: decorator
@builtin
export declare const HEAP_BASE: usize;
// @ts-ignore: decorator
@builtin
export declare const RTTI_BASE: usize;
// @ts-ignore: decorator
@builtin
export declare function idof<T>(): u32;
// @ts-ignore: decorator
@builtin @unsafe
export declare function __visit_globals(cookie: u32): void;
// @ts-ignore: decorator
@builtin @unsafe
export declare function __visit_members(ref: usize, cookie: u32): void;

View File

@ -1,113 +0,0 @@
Garbage collector interface
===========================
A garbage collector for AssemblyScript must implement the following common and either the tracing or reference counting interfaces:
Common
------
* **__ref_collect**(): `void`<br />
Triggers a full garbage collection cycle. Also indicates the presence of a GC.
Tracing
-------
* **__ref_register**(ref: `usize`): `void`<br />
Sets up a new reference.
* **__ref_link**(ref: `usize`, parentRef: `usize`): `void`<br />
Links a reference to a parent that is now referencing it.
* **__ref_unlink**(ref: `usize`, parentRef: `usize`): `void`<br />
Unlinks a reference from a parent that was referencing it. Implementation is optional.
* **__ref_mark**(ref: `usize`): `void`<br />
Marks a reference as being reachable so it doesn't become sweeped.
Reference counting
------------------
* **__ref_register**(ref: `usize`): `void`<br />
Sets up a new reference. Implementation is optional.
* **__ref_retain**(ref: `usize`): `void`<br />
Retains a reference, usually incrementing RC.
* **__ref_release**(ref: `usize`): `void`<br />
Releases a reference, usually decrementing RC.
Typical patterns
----------------
Standard library components make use of the interface where managed references are stored or deleted. Common patterns are:
### General
```ts
/// <reference path="./collector/index.d.ts" />
if (isManaged<T>()) {
// compiled only if T is a managed reference
... pattern ...
}
```
### Insertion
```ts
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, parentRef);
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(ref, parentRef);
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
```
### Replacement
```ts
if (ref !== oldRef) {
if (isNullable<T>()) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) if (oldRef) __ref_unlink(oldRef, parentRef);
if (ref) __ref_link(ref, parentRef);
} else if (isDefined(__ref_retain)) {
if (oldRef) __ref_release(oldRef);
if (ref) __ref_retain(ref);
} else assert(false);
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) if (oldRef) __ref_unlink(oldRef, parentRef); // *
__ref_link(ref, parentRef);
} else if (isDefined(__ref_retain)) {
if (oldRef) __ref_release(oldRef); // *
__ref_retain(ref);
} else assert(false);
}
}
```
### Deletion
```ts
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(ref, parentRef);
} else if (isDefined(__ref_retain)) __ref_release(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(ref, parentRef);
} else if (isDefined(__ref_retain)) __ref_release(ref);
else assert(false);
}
```
(*) Note that some data structures may contain `null` values even though the value type isn't nullable. May be the case when appending a new element to an array for example.

View File

@ -1,37 +0,0 @@
// A tracing dummy GC.
// @ts-ignore: decorator
@inline
const TRACE = isDefined(GC_TRACE);
// @ts-ignore: decorator
@global @unsafe
function __ref_register(ref: usize): void {
if (TRACE) trace("dummy.register", 1, ref);
}
// @ts-ignore: decorator
@global @unsafe
function __ref_collect(): void {
if (TRACE) trace("dummy.collect");
}
// Tracing
// @ts-ignore: decorator
@global @unsafe
function __ref_link(ref: usize, parentRef: usize): void {
if (TRACE) trace("dummy.link", 2, ref, parentRef);
}
// @ts-ignore: decorator
@global @unsafe
function __ref_unlink(ref: usize, parentRef: usize): void {
if (TRACE) trace("dummy.unlink", 2, ref, parentRef);
}
// @ts-ignore: decorator
@global @unsafe
function __ref_mark(ref: usize): void {
if (TRACE) trace("dummy.mark", 1, ref);
}

View File

@ -1,29 +0,0 @@
// A reference counting dummy GC.
// @ts-ignore: decorator
@inline
const TRACE = isDefined(GC_TRACE);
// @ts-ignore: decorator
@global @unsafe
function __ref_register(ref: usize): void {
if (TRACE) trace("dummyrc.register", 1, ref);
}
// @ts-ignore: decorator
@global @unsafe
function __ref_collect(): void {
if (TRACE) trace("dummyrc.collect");
}
// @ts-ignore: decorator
@global @unsafe
function __ref_retain(ref: usize): void {
if (TRACE) trace("dummyrc.retain", 1, ref);
}
// @ts-ignore: decorator
@global @unsafe
function __ref_release(ref: usize): void {
if (TRACE) trace("dummyrc.release", 1, ref);
}

View File

@ -1,15 +0,0 @@
// common
declare function __ref_collect(): void;
declare function __ref_register(ref: usize): void;
// tracing
declare function __ref_link(ref: usize, parentRef: usize): void;
declare function __ref_unlink(ref: usize, parentRef: usize): void;
declare function __ref_mark(ref: usize): void;
// reference counting
declare function __ref_retain(ref: usize): void;
declare function __ref_release(ref: usize): void;
// debugging
declare const GC_TRACE: bool;

View File

@ -1,258 +0,0 @@
// Largely based on Bach Le's μgc, see: https://github.com/bullno1/ugc
// @ts-ignore: decorator
@inline
const TRACE = isDefined(GC_TRACE);
import { HEADER_SIZE } from "../util/runtime";
import { __gc_mark_roots, __gc_mark_members } from "../runtime";
/** Collector states. */
const enum State {
/** Not yet initialized. */
INIT = 0,
/** Currently transitioning from SWEEP to MARK state. */
IDLE = 1,
/** Currently marking reachable objects. */
MARK = 2,
/** Currently sweeping unreachable objects. */
SWEEP = 3
}
/** Current collector state. */
// @ts-ignore: decorator
@lazy
var state = State.INIT;
/** Current white color value. */
// @ts-ignore: decorator
@lazy
var white = 0;
// From and to spaces
// @ts-ignore: decorator
@lazy
var fromSpace: ManagedObjectList;
// @ts-ignore: decorator
@lazy
var toSpace: ManagedObjectList;
// @ts-ignore: decorator
@lazy
var iter: ManagedObject;
// ╒═══════════════ Managed object 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┼─┼─┴─┤ ┐
// │ next │0│ C │ ◄─┐ = nextWithColor
// ├─────────────────────────────────────────────────────────┴─┴───┤ │ usize
// │ prev │ ◄─┘
// ├───────────────────────────────────────────────────────────────┤
// │ hookFn │
// ╞═══════════════════════════════════════════════════════════════╡ SIZE ┘ ◄─ user-space reference
// │ ... data ... │
// └───────────────────────────────────────────────────────────────┘
// C: color
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
@unmanaged class ManagedObject {
// <HEADER>
classId: u32;
payloadSize: u32;
// </HEADER>
/** Pointer to the next object with color flags stored in the alignment bits. */
nextWithColor: usize;
/** Pointer to the previous object. */
prev: ManagedObject;
/** Class-specific hook function called with the user-space reference. */
get hookFn(): (ref: usize) => void {
return changetype<(ref: usize) => void>(this.classId);
}
/** Gets the pointer to the next object. */
get next(): ManagedObject {
return changetype<ManagedObject>(this.nextWithColor & ~3);
}
/** Sets the pointer to the next object. */
set next(obj: ManagedObject) {
this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & 3);
}
/** Gets this object's color. */
get color(): i32 {
return this.nextWithColor & 3;
}
/** Sets this object's color. */
set color(color: i32) {
this.nextWithColor = (this.nextWithColor & ~3) | color;
}
/** Unlinks this object from its list. */
unlink(): void {
var next = this.next;
var prev = this.prev;
if (TRACE) trace(" unlink [pref, ref, next]", 3, objToRef(prev), objToRef(this), objToRef(next));
next.prev = prev;
prev.next = next;
}
/** Marks this object as gray, that is reachable with unscanned children. */
makeGray(): void {
if (TRACE) trace(" makeGray", 1, objToRef(this));
const gray = 2;
if (this == iter) iter = this.prev;
this.unlink();
toSpace.push(this);
this.nextWithColor = (this.nextWithColor & ~3) | gray;
}
}
/** A list of managed objects. Used for the from and to spaces. */
@unmanaged class ManagedObjectList extends ManagedObject {
/** Inserts an object. */
push(obj: ManagedObject): void {
var prev = this.prev;
if (TRACE) trace(" push [prev, ref, next]", 3, objToRef(prev), objToRef(obj), objToRef(this));
obj.next = this;
obj.prev = prev;
prev.next = obj;
this.prev = obj;
}
/** Clears this list. */
clear(): void {
if (TRACE) trace(" clear", 1, objToRef(this));
this.nextWithColor = changetype<usize>(this);
this.prev = this;
}
}
function maybeInit(): void {
if (state == State.INIT) {
if (TRACE) trace("itcm~init");
fromSpace = changetype<ManagedObjectList>(memory.allocate(HEADER_SIZE));
if (TRACE) trace(" fromSpace =", 1, objToRef(fromSpace));
fromSpace.classId = -1; // would error
fromSpace.payloadSize = 0;
fromSpace.clear();
toSpace = changetype<ManagedObjectList>(memory.allocate(HEADER_SIZE));
if (TRACE) trace(" toSpace =", 1, objToRef(toSpace));
toSpace.classId = -1; // would error
toSpace.payloadSize = 0;
toSpace.clear();
iter = toSpace;
state = State.IDLE;
if (TRACE) trace("itcm~state = IDLE");
}
}
/** Performs a single step according to the current state. */
function step(): void {
var obj: ManagedObject;
switch (state) {
case State.INIT: unreachable();
case State.IDLE: {
if (TRACE) trace("itcm~step/IDLE");
__gc_mark_roots();
state = State.MARK;
if (TRACE) trace("itcm~state = MARK");
break;
}
case State.MARK: {
obj = iter.next;
if (obj !== toSpace) {
if (TRACE) trace("itcm~step/MARK", 1, objToRef(obj));
iter = obj;
obj.color = i32(!white);
__gc_mark_members(obj.classId, objToRef(obj));
} else {
__gc_mark_roots();
if (TRACE) trace("itcm~step/MARK finish");
obj = iter.next;
if (obj === toSpace) {
let from = fromSpace;
fromSpace = toSpace;
toSpace = from;
white = i32(!white);
iter = from.next;
state = State.SWEEP;
if (TRACE) trace("itcm~state = SWEEP");
}
}
break;
}
case State.SWEEP: {
obj = iter;
if (obj !== toSpace) {
if (TRACE) trace("itcm~step/SWEEP free", 1, objToRef(obj));
iter = obj.next;
if (changetype<usize>(obj) >= HEAP_BASE) memory.free(changetype<usize>(obj));
} else {
if (TRACE) trace("itcm~step/SWEEP finish");
toSpace.clear();
state = State.IDLE;
if (TRACE) trace("itcm~state = IDLE");
}
break;
}
}
}
// @ts-ignore: decorator
@inline
function refToObj(ref: usize): ManagedObject {
return changetype<ManagedObject>(ref - HEADER_SIZE);
}
// @ts-ignore: decorator
@inline
function objToRef(obj: ManagedObject): usize {
return changetype<usize>(obj) + HEADER_SIZE;
}
// Garbage collector interface
// @ts-ignore: decorator
@global @unsafe
export function __ref_collect(): void {
if (TRACE) trace("itcm.collect");
maybeInit();
// finish the current state
while (state != State.IDLE) step();
// perform a full cycle
do step(); while (state != State.IDLE);
}
// @ts-ignore: decorator
@global @unsafe
export function __ref_register(ref: usize): void {
if (TRACE) trace("itcm.register", 1, ref);
maybeInit();
var obj = refToObj(ref);
obj.color = white;
fromSpace.push(obj); // sets gc-reserved header fields
}
// @ts-ignore: decorator
@global @unsafe
export function __ref_link(ref: usize, parentRef: usize): void {
if (TRACE) trace("itcm.link", 2, ref, parentRef);
maybeInit();
var parent = refToObj(parentRef);
if (parent.color == i32(!white) && refToObj(ref).color == white) parent.makeGray();
}
// @ts-ignore: decorator
@global @unsafe
export function __ref_mark(ref: usize): void {
if (TRACE) trace("itcm.mark", 1, ref);
maybeInit();
var obj = refToObj(ref);
if (obj.color == white) obj.makeGray();
}

View File

@ -1,269 +0,0 @@
// A Pure Reference Counting Garbage Collector
//
// After the paper by D. Bacon et al., 2001, IBM T.J. Watson Research Center
// https://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon03Pure.pdf
import { HEADER_SIZE } from "../util/runtime";
ERROR("not implemented");
/* tslint:disable */
// TODO: new builtins
declare function ITERATECHILDREN(s: Header, fn: (t: Header) => void): void;
declare function ISACYCLIC(s: Header): bool;
/** Object Colorings for Cycle Collection */
const enum Color {
/** In use or free. */
BLACK = 0,
/** Possible member of cycle. */
GRAY = 1,
/** Member of garbage cycle. */
WHITE = 2,
/** Possible root of cycle. */
PURPLE = 3,
/** Acyclic. */
GREEN = 4
}
// TODO: this is a placeholder -> map this to HEADER
class Header {
rc: u32;
color: Color;
buffered: bool;
}
// When reference counts are decremented, we place potential roots of cyclic garbage into a buffer
// called Roots. Periodically, we process this buffer and look for cycles by subtracting internal
// reference counts.
var rootsBuffer: usize = 0;
var rootsOffset: usize = 0; // insertion offset
var rootsLength: usize = 0; // insertion limit
function appendRoot(s: Header): void {
if (rootsOffset >= rootsLength) {
// grow for now
let newLength = rootsLength ? 2 * rootsLength : 256 * sizeof<usize>();
let newBuffer = memory.allocate(newLength);
memory.copy(newBuffer, rootsBuffer, rootsOffset);
memory.free(rootsBuffer);
rootsBuffer = newBuffer;
rootsLength = newLength;
}
store<usize>(rootsBuffer + rootsOffset, s);
rootsOffset += sizeof<usize>();
}
function systemFree(s: Header): void {
memory.free(changetype<usize>(s));
}
// When a reference to a node S is created, the reference count of T is incremented and it is
// colored black, since any object whose reference count was just incremented can not be garbage.
function increment(s: Header): void {
s.rc += 1;
s.color = ISACYCLIC(s) ? Color.GREEN : Color.BLACK; // TODO: is this about correct?
}
// When a reference to a node S is deleted, the reference count is decremented. If the reference
// count reaches zero, the procedure Release is invoked to free the garbage node. If the reference
// count does not reach zero, the node is considered as a possible root of a cycle.
function decrement(s: Header): void {
s.rc -= 1;
if (s.color == Color.GREEN) { // if (ISACYCLIC<T>()) { ... }
if (!s.rc) systemFree(s);
// TODO: is this correct? here, if `decrement` was generic (propagate from UNLINK<T,TParent>)
// the green condition could be eliminated both here and in increment (just using black).
// acyclic types also don't need ITERATECHILDREN then as these really just inc/dec/free.
} else {
if (!s.rc) release(s);
else possibleRoot(s);
}
}
// When the reference count of a node reaches zero, the contained pointers are deleted, the object
// is colored black, and unless it has been buffered, it is freed. If it has been buffered, it is
// in the Roots buffer and will be freed later (in the procedure MarkRoots).
function release(s: Header): void {
ITERATECHILDREN(s, t => decrement(t)); // TODO: skip if acyclic ?
s.color = Color.BLACK;
if (!s.buffered) systemFree(s);
}
// When the reference count of S is decremented but does not reach zero, it is considered as a
// possible root of a garbage cycle. If its color is already purple, then it is already a candidate
// root; if not, its color is set to purple. Then the buffered flag is checked to see if it has
// been purple since we last performed a cycle collection. If it is not buffered, it is added to
// the buffer of possible roots.
function possibleRoot(s: Header): void {
if (s.color != Color.PURPLE) {
s.color = Color.PURPLE;
if (!s.buffered) {
s.buffered = true;
appendRoot(s);
}
}
}
// When the root buffer is full, or when some other condition, such as low memory occurs, the
// actual cycle collection operation is invoked. This operation has three phases: MarkRoots, which
// removes internal reference counts; ScanRoots, which restores reference counts when they are
// non-zero; and finally CollectRoots, which actually collects the cyclic garbage.
function collectCycles(): void {
markRoots();
scanRoots();
collectRoots();
}
// The marking phase looks at all the nodes S whose pointers have been stored in the Roots buffer
// since the last cycle collection. If the color of the node is purple (indicating a possible root
// of a garbage cycle) and the reference count has not become zero, then MarkGray(S) is invoked to
// perform a depth-first search in which the reached nodes are colored gray and internal reference
// counts are subtracted. Otherwise, the node is removed from the Roots buffer, the buffered flag
// is cleared, and if the reference count is zero the object is freed.
function markRoots(): void {
var readOffset = rootsBuffer;
var writeOffset = readOffset;
var readLimit = readOffset + rootsOffset;
while (readOffset < readLimit) {
let s = load<Header>(readOffset);
if (s.color == Color.PURPLE && s.rc > 0) {
markGray(s);
store<Header>(writeOffset, s);
writeOffset += sizeof<usize>();
} else {
s.buffered = false;
// remove from roots
if (s.color == Color.BLACK && !s.rc) systemFree(s);
}
readOffset += sizeof<usize>();
}
rootsOffset = writeOffset - rootsBuffer;
}
// For each node S that was considered by MarkGray(S), this procedure invokes Scan(S) to either
// color the garbage subgraph white or re-color the live subgraph black.
function scanRoots(): void {
var readOffset = rootsBuffer;
var readLimit = readOffset + rootsOffset;
while (readOffset < readLimit) {
scan(load<Header>(readOffset));
readOffset += sizeof<usize>();
}
}
// After the ScanRoots phase of the CollectCycles procedure, any remaining white nodes will be
// cyclic garbage and will be reachable from the Roots buffer. This prodecure invokes CollectWhite
// for each node in the Roots buffer to collect the garbage; all nodes in the root buffer are
// removed and their buffered flag is cleared.
function collectRoots(): void {
var readOffset = rootsBuffer;
var readLimit = readOffset + rootsOffset;
while (readOffset < readLimit) {
let s = load<Header>(readOffset);
// remove from roots
s.buffered = false;
collectWhite(s);
}
rootsOffset = 0;
}
// This procedure performs a simple depth-first traversal of the graph beginning at S, marking
// visited nodes gray and removing internal reference counts as it goes.
function markGray(s: Header): void {
if (s.color != Color.GRAY) {
s.color = Color.GRAY;
ITERATECHILDREN(s, t => {
t.rc -= 1;
markGray(t);
});
}
}
// If this procedure finds a gray object whose reference count is greater than one, then that
// object and everything reachable from it are live data; it will therefore call ScanBlack(S) in
// order to re-color the reachable subgraph and restore the reference counts subtracted by
// MarkGray. However, if the color of an object is gray and its reference count is zero, then it is
// colored white, and Scan is invoked upon its chldren. Note that an object may be colored white
// and then re-colored black if it is reachable from some subsequently discovered live node.
function scan(s: Header): void {
if (s.color == Color.GRAY) {
if (s.rc > 0) scanBlack(s);
else {
s.color = Color.WHITE;
ITERATECHILDREN(s, t => scan(t));
}
}
}
// This procedure performs the inverse operation of MarkGray, visiting the nodes, changing the
// color of objects back to black, and restoring their reference counts.
function scanBlack(s: Header): void {
s.color = Color.BLACK;
ITERATECHILDREN(s, t => {
t.rc += 1;
if (t.color != Color.BLACK) scanBlack(t);
});
}
// This procedure recursively frees all white objects, re-coloring them black as it goes. If a
// white object is buffered, it is not freed; it will be freed later when it is found in the Roots
// buffer.
function collectWhite(s: Header): void {
if (s.color == Color.WHITE && !s.buffered) {
s.color = Color.BLACK;
ITERATECHILDREN(s, t => collectWhite(t));
systemFree(s);
}
}
// Garbage collector interface
// @ts-ignore: decorator
@global @unsafe
function __ref_collect(): void {
collectCycles();
}
// @ts-ignore: decorator
@global @unsafe
function __ref_retain(ref: usize): void {
increment(changetype<Header>(ref - HEADER_SIZE));
}
// @ts-ignore: decorator
@global @unsafe
function __ref_release(ref: usize): void {
decrement(changetype<Header>(ref - HEADER_SIZE))
}
// TODO:
// A significant constant-factor improvement can be obtained for cycle collection by observing that
// some objects are inherently acyclic. We speculate that they will comprise the majorits of
// objects in many applications. Therefore, if we can avoid cycle collection for inherently acyclic
// object, we will significantly reduce the overhead of cycle collection as a whole. [...]
//
// Acyclic classes may contain:
// - scalars;
// - references to classes that are both acyclic and final; and
// - arrays of either of the above.
//
// Our implementation marks objects whose class is acyclic with the special color green. Green
// objects are ignored by the cycle collection algorithm, except that when a dead cycle refers to
// green objects, they are collected along with the dead cycle.

View File

@ -1,4 +1,4 @@
import { MAX_BYTELENGTH } from "./util/runtime";
import { BLOCK_MAXSIZE } from "./rt/common";
import { ArrayBuffer } from "./arraybuffer";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH } from "./util/error";
@ -16,10 +16,10 @@ export class DataView {
byteLength: i32 = buffer.byteLength
) {
if (
i32(<u32>byteLength > <u32>MAX_BYTELENGTH) |
i32(<u32>byteLength > <u32>BLOCK_MAXSIZE) |
i32(<u32>byteOffset + byteLength > <u32>buffer.byteLength)
) throw new RangeError(E_INVALIDLENGTH);
this.data = buffer; // links
this.data = buffer; // retains
var dataStart = changetype<usize>(buffer) + <usize>byteOffset;
this.dataStart = dataStart;
this.dataLength = byteLength;

View File

@ -1,5 +1,7 @@
import { HEADER, HEADER_SIZE, MAX_BYTELENGTH, allocate, register } from "./util/runtime";
import { __runtime_id, __gc_mark_members } from "./runtime";
/// <reference path="./rt/index.d.ts" />
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
import { idof } from "./builtins";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error";
// NOTE: DO NOT USE YET!
@ -11,20 +13,20 @@ export class FixedArray<T> {
[key: number]: T;
constructor(length: i32) {
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
if (<u32>length > <u32>BLOCK_MAXSIZE >>> alignof<T>()) throw new RangeError(E_INVALIDLENGTH);
if (isReference<T>()) {
if (!isNullable<T>()) {
if (length) throw new Error(E_HOLEYARRAY);
}
}
var outSize = <usize>length << alignof<T>();
var out = allocate(outSize);
var out = __alloc(outSize, idof<FixedArray<T>>());
memory.fill(out, 0, outSize);
return changetype<FixedArray<T>>(register(out, __runtime_id<FixedArray<T>>()));
return changetype<FixedArray<T>>(out); // retains
}
get length(): i32 {
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >>> alignof<T>();
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >>> alignof<T>();
}
@operator("[]") private __get(index: i32): T {
@ -44,26 +46,10 @@ export class FixedArray<T> {
@operator("{}=") private __unchecked_set(index: i32, value: T): void {
if (isManaged<T>()) {
let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
let oldValue = load<T>(offset);
if (value !== oldValue) {
store<T>(offset, value);
if (oldValue !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
if (isNullable<T>()) {
if (value !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
let oldValue = load<usize>(offset);
if (changetype<usize>(value) != oldValue) {
store<usize>(offset, __retain(changetype<usize>(value)));
__release(changetype<usize>(oldValue));
}
} else {
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);
@ -72,20 +58,20 @@ export class FixedArray<T> {
// GC integration
@unsafe private __traverse(): void {
@unsafe private __traverse(cookie: u32): void {
if (isManaged<T>()) {
let cur = changetype<usize>(this);
let end = cur + changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize;
let end = cur + changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize;
while (cur < end) {
let val = load<usize>(cur);
if (isNullable<T>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<T>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<T>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
cur += sizeof<usize>();
}

View File

@ -1,7 +1,6 @@
/// <reference path="./collector/index.d.ts" />
/// <reference path="./rt/index.d.ts" />
import { HASH } from "./util/hash";
import { __runtime_id, __gc_mark_members } from "./runtime";
// A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
@ -83,7 +82,7 @@ export class Map<K,V> {
}
private find(key: K, hashCode: u32): MapEntry<K,V> | null {
var entry = load<MapEntry<K,V>>(
var entry = load<MapEntry<K,V>>( // unmanaged!
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
);
while (entry) {
@ -106,35 +105,9 @@ export class Map<K,V> {
var hashCode = HASH<K>(key);
var entry = this.find(key, hashCode); // unmanaged!
if (entry) {
if (isManaged<V>()) {
let oldValue = entry.value;
if (value !== oldValue) {
entry.value = value;
if (isNullable<V>()) {
if (oldValue !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
if (value !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
__ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (isDefined(__ref_retain)) {
__ref_release(changetype<usize>(oldValue));
__ref_retain(changetype<usize>(value));
} else assert(false);
}
}
} else {
entry.value = value;
}
entry.value = isManaged<V>()
? changetype<V>(__retainRelease(changetype<usize>(value), changetype<usize>(entry.value)))
: value;
} else {
// check if rehashing is necessary
if (this.entriesOffset == this.entriesCapacity) {
@ -147,35 +120,13 @@ export class Map<K,V> {
// append new entry
let entries = this.entries;
entry = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>());
entry.key = key;
entry.value = value;
// link with the map
if (isManaged<K>()) {
if (isNullable<K>()) {
if (key !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
}
if (isManaged<V>()) {
if (isNullable<V>()) {
if (value !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(value), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(value));
else assert(false);
}
}
entry.key = isManaged<K>()
? changetype<K>(__retain(changetype<usize>(key)))
: key;
entry.value = isManaged<V>()
? changetype<V>(__retain(changetype<usize>(value)))
: value;
++this.entriesCount;
// link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -187,38 +138,8 @@ export class Map<K,V> {
delete(key: K): bool {
var entry = this.find(key, HASH<K>(key));
if (!entry) return false;
if (isManaged<K>()) {
let oldKey = entry.key;
if (isNullable<K>()) {
if (oldKey !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldKey), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldKey));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldKey), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldKey));
else assert(false);
}
}
if (isManaged<V>()) {
let oldValue = entry.key;
if (isNullable<V>()) {
if (oldValue !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
}
if (isManaged<K>()) __release(changetype<usize>(entry.key));
if (isManaged<V>()) __release(changetype<usize>(entry.value));
entry.taggedNext |= EMPTY;
--this.entriesCount;
// check if rehashing is appropriate
@ -268,10 +189,10 @@ export class Map<K,V> {
// GC integration
@unsafe private __traverse(): void {
__ref_mark(changetype<usize>(this.buckets));
@unsafe private __traverse(cookie: u32): void {
__visit(changetype<usize>(this.buckets), cookie);
var entries = this.entries;
__ref_mark(changetype<usize>(entries));
__visit(changetype<usize>(entries), cookie);
if (isManaged<K>() || isManaged<V>()) {
let cur = changetype<usize>(entries);
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K,V>();
@ -282,24 +203,24 @@ export class Map<K,V> {
let val = changetype<usize>(entry.key);
if (isNullable<K>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
}
if (isManaged<V>()) {
let val = changetype<usize>(entry.value);
if (isNullable<V>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<V>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<V>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
}
}

View File

@ -1,20 +1,9 @@
/// <reference path="./allocator/index.d.ts" />
import { memcmp, memmove, memset } from "./util/memory";
import { E_NOTIMPLEMENTED } from "./util/error";
// @ts-ignore: decorator
@builtin
export declare const HEAP_BASE: usize;
/** Memory manager interface. */
export namespace memory {
/** Whether the memory managed interface is implemented. */
// @ts-ignore: decorator
@lazy
export const implemented: bool = isDefined(__mem_allocate);
/** Gets the size of the memory in pages. */
// @ts-ignore: decorator
@builtin
@ -53,30 +42,6 @@ export namespace memory {
throw new Error(E_NOTIMPLEMENTED);
}
/** Dynamically allocates a section of memory and returns its address. */
// @ts-ignore: decorator
@unsafe
export function allocate(size: usize): usize {
if (isDefined(__mem_allocate)) return __mem_allocate(size);
else throw new Error(E_NOTIMPLEMENTED);
}
/** Dynamically frees a section of memory by the previously allocated address. */
// @ts-ignore: decorator
@unsafe
export function free(ptr: usize): void {
if (isDefined(__mem_free)) __mem_free(ptr);
else throw new Error(E_NOTIMPLEMENTED);
}
/** Resets the memory to its initial state. Arena allocator only. */
// @ts-ignore: decorator
@unsafe
export function reset(): void {
if (isDefined(__mem_reset)) __mem_reset();
else throw new Error(E_NOTIMPLEMENTED);
}
/** Repeats a section of memory at a specific address. */
// @ts-ignore: decorator
@unsafe

View File

@ -8,29 +8,68 @@ It is based on [the TLSF memory manager](./tlsf.ts) and [a pure reference counti
Interface
---------
* **__rt_allocate**(size: `usize`, id: `u32` = 0): `usize`<br />
* **__alloc**(size: `usize`, id: `u32` = 0): `usize`<br />
Dynamically allocates a chunk of memory of at least the specified size and returns its address.
Alignment is guaranteed to be 16 bytes to fit up to v128 values naturally.
* **__rt_reallocate**(ref: `usize`, size: `usize`): `usize`<br />
* **__realloc**(ref: `usize`, size: `usize`): `usize`<br />
Dynamically changes the size of a chunk of memory, possibly moving it to a new address.
* **__rt_free**(ref: `usize`): `void`<br />
* **__free**(ref: `usize`): `void`<br />
Frees a dynamically allocated chunk of memory by its address.
* **__rt_retain**(ref: `usize`): `void`<br />
Retains a reference.
* **__retain**(ref: `usize`): `void`<br />
Retains a reference to an instance of a reference type. The instance doesn't become collected as long as there's at least one retained reference.
* **__rt_release**(ref: `usize`): `void`<br />
Releases a reference.
* **__release**(ref: `usize`): `void`<br />
Releases a reference to an instance of a reference type. The instance is considered for collection once all references to it have been released.
* **__rt_collect**(): `void`<br />
Forces a full garbage collection cycle.
* **__collect**(): `void`<br />
Forces a full garbage collection cycle. By default this means that reference cycles are resolved and possibly collected.
* **__rt_typeinfo**(id: `u32`): `void`<br />
Obtains the runtime type information for objects of the kind represented by the specified id.
* **__visit**(ref: `usize`, cookie: `u32`): `void`<br />
Concrete visitor implementation called during traversal. Cookie can be used to indicate one of multiple operations.
Built-ins
---------
The following functions are generated by the compiler based on compile-time information that wouldn't be available or inefficient to provide otherwise.
* **__info**(id: `u32`): `void`<br />
Obtains the runtime type information for objects with the specified runtime id. Runtime type information is a set of flags indicating whether a reference type is managed, an array or similar, and what the relevant alignments when creating an instance are etc.
* **__visit_globals**(cookie: `u32`)<br />
Calls `__visit` on each global that is of a reference type. Not used anymore (originally provided to support tracing GCs) but still here for possible future use.
* **__visit_members**(ref: `usize`, cookie: `u32`)<br />
Calls `__visit` on each member of the instance pointed to by `ref` that is of a reference type.
Stub
----
The fully functional yet minimal [stub implementation](./stub.ts) provides dynamic memory allocation only but doesn't include sophisticated support to deallocate objects. Useful for prototyping or very short-lived programs with hardly any memory footprint.
A fully functional yet minimal (as in code size) [stub implementation](./index-stub.ts) that provides dynamic memory allocation but no deallocation. Useful for prototyping or very short-lived programs with hardly any memory footprint. The [none implementation](./index-none.ts) is the same as the stub implementation without any runtime exports.
Integration notes
-----------------
Working with the runtime internals within standard library code can be tricky and requires knowledge of where the compiler will insert runtime calls automatically. For example, whenever a value of a reference type is assigned to a local, a global or a field, the compiler *might* insert a `__retain` call, respectively whenever such a value becomes unassigned from one, *might* insert a `__release` call. When a value is handled as an `usize` (i.e. when it comes from `__alloc` or is `changetype<usize>`ed), no such insertion happens (afterwards), but as soon as a `changetype<RefType>`ed (again), the side-effects introduced by automatic insertion must be understood.
A `__retain` call is inserted when a value of a reference type
* is assigned to a local, global or a field **if** the value is not already the exact same value as stored before
* is an argument to a function call, including `this` (i.e. `str.indexOf` retains `str`)
* is returned from a function (i.e. no need to manually `__retain` if explicitly `changetype`d)
A `__release` call is inserted when a value of a reference type
* becomes unassigned from a local, global or a field due to assigning a new value **if** the value is not already the exact same value as stored before
* is popped together with its local from the current scope, i.e. a local declared with `let` in a block, or otherwise at the end of a function
If not taken into account properly
* a memory leak will occur when `__retain`ed more often than intended
* a double-free will occur when `__release`d more often than intended
Also note that a `load<T>(x)` with a reference type acts like a `changetype<T>(load<usize>(x))` and does not `__retain` unless the result is assigned to a local.
Some best practices are:
* Use the fresh `__alloc`ed reference in `usize` form where possible, e.g. when just copying raw bytes is necessary, and `changetype` it once on return.
* When providing such a `usize` reference to a function, if the value isn't needed anymore afterwards, just `changetype` it on the call which will `__retain` and `__release` it automatically, including freeing it if wasn't retained before, or, if still needed afterwards, assign the `changetype`d reference to a local first and provide the local as the argument, hence keeping the reference alive as long as the local or any subsequent target is.
* If it's not avoidable to `changetype` to the actual reference type, do it inline in an expression and avoid assigning to a local.

View File

@ -12,10 +12,22 @@
// @ts-ignore: decorator
@inline export const DEBUG = true;
/** Common block structure. */
@unmanaged export class CommonBlock {
// ╒════════════════ Common 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
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
// │ MM info │ -16
// ├───────────────────────────────────────────────────────────────┤
// │ GC info │ -12
// ├───────────────────────────────────────────────────────────────┤
// │ runtime id │ -8
// ├───────────────────────────────────────────────────────────────┤
// │ runtime size │ -4
// ╞═══════════════════════════════════════════════════════════════╡
// │ ... │ ref
@unmanaged export class BLOCK {
/** Memory manager info. */
mmInfo: usize; // WASM64 might need adaption
mmInfo: usize; // WASM64 needs adaption
/** Garbage collector info. */
gcInfo: u32;
/** Runtime class id. */
@ -24,16 +36,53 @@
rtSize: u32;
}
/////////////////////////////////// Type information interface ////////////////////////////////////
import { RTTI_BASE } from "../runtime";
import { RTTIData } from "../common/rtti";
// @ts-ignore: decorator
@inline export const BLOCK_OVERHEAD = (offsetof<BLOCK>() + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@global @unsafe
function __rt_typeinfo(id: u32): u32 {
var ptr: usize = RTTI_BASE;
@inline export const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD;
/////////////////////////////////// Type information interface ////////////////////////////////////
import { RTTI_BASE } from "builtins";
import { RTTIData, RTTIFlags } from "common/rtti";
// @ts-ignore: decorator
@unsafe @global
export function __typeinfo(id: u32): RTTIFlags {
var ptr = RTTI_BASE;
return !id || id > load<u32>(ptr)
? unreachable()
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
}
// @ts-ignore: decorator
@unsafe @global
export function __instanceof(ref: usize, superId: u32): bool { // keyword
var id = changetype<BLOCK>(ref - BLOCK_OVERHEAD).rtId;
var ptr = RTTI_BASE;
if (id && id <= load<u32>(ptr)) {
do if (id == superId) return true;
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
}
return false;
}
///////////////////////////////////////////// Helpers /////////////////////////////////////////////
import { idof } from "builtins";
import { ArrayBufferView } from "arraybuffer";
// @ts-ignore: decorator
@unsafe @global
export function __allocArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
var array = __alloc(offsetof<i32[]>(), id);
var bufferSize = <usize>length << alignLog2;
var buffer = __alloc(bufferSize, idof<ArrayBuffer>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // TODO/RT: retains
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
if (data) memory.copy(buffer, data, bufferSize);
return array;
}

View File

@ -0,0 +1,3 @@
export { __alloc, __realloc, __free } from "./tlsf";
export { __retain, __release, __collect } from "./purerc";
export { __instanceof, __typeinfo } from "./common";

View File

@ -0,0 +1 @@
import "rt/index-stub";

View File

@ -1,12 +1,4 @@
import { AL_MASK, CommonBlock } from "./common";
// @ts-ignore: decorator
@inline
const BLOCK_OVERHEAD = (offsetof<CommonBlock>() + AL_MASK) & ~AL_MASK;
// @ts-ignore: decorator
@inline
const BLOCK_MAXSIZE: usize = (1 << 30) - BLOCK_OVERHEAD; // match TLSF
import { AL_MASK, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
// @ts-ignore: decorator
@lazy
@ -16,11 +8,9 @@ var startOffset: usize = (HEAP_BASE + AL_MASK) & ~AL_MASK;
@lazy
var offset: usize = startOffset;
//////////////////////////////////// Memory manager interface /////////////////////////////////////
// @ts-ignore: decorator
@unsafe @global
export function __rt_allocate(size: usize, id: u32): usize {
export function __alloc(size: usize, id: u32): usize {
if (size > BLOCK_MAXSIZE) unreachable();
var ptr = offset + BLOCK_OVERHEAD;
var newPtr = (ptr + max<usize>(size, 1) + AL_MASK) & ~AL_MASK;
@ -33,7 +23,7 @@ export function __rt_allocate(size: usize, id: u32): usize {
}
}
offset = newPtr;
var block = changetype<CommonBlock>(ptr - BLOCK_OVERHEAD);
var block = changetype<BLOCK>(ptr - BLOCK_OVERHEAD);
block.rtId = id;
block.rtSize = size;
return ptr;
@ -41,11 +31,11 @@ export function __rt_allocate(size: usize, id: u32): usize {
// @ts-ignore: decorator
@unsafe @global
export function __rt_reallocate(ref: usize, size: usize): usize {
var block = changetype<CommonBlock>(ref - BLOCK_OVERHEAD);
export function __realloc(ref: usize, size: usize): usize {
var block = changetype<BLOCK>(ref - BLOCK_OVERHEAD);
var oldSize = <usize>block.rtSize;
if (size > oldSize) {
let newRef = __rt_allocate(size, block.rtId);
let newRef = __alloc(size, block.rtId);
memory.copy(newRef, ref, oldSize);
ref = newRef;
} else {
@ -56,30 +46,40 @@ export function __rt_reallocate(ref: usize, size: usize): usize {
// @ts-ignore: decorator
@unsafe @global
export function __rt_free(ref: usize): void {
export function __free(ref: usize): void {
}
// @ts-ignore: decorator
@unsafe @global
export function __rt_reset(): void { // special
offset = startOffset;
}
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
// @unsafe @global
// export function __reset(): void { // special
// offset = startOffset;
// }
// @ts-ignore: decorator
@global @unsafe
export function __rt_retain(ref: usize): void {
export function __retain(ref: usize): usize {
return ref;
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_release(ref: usize): void {
export function __release(ref: usize): void {
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_collect(): void {
function __visit(ref: usize, cookie: u32): void {
}
export { __rt_typeinfo };
// @ts-ignore: decorator
@global @unsafe
function __retainRelease(ref: usize, oldRef: usize): usize {
return ref;
}
// @ts-ignore: decorator
@global @unsafe
export function __collect(): void {
}
export { __instanceof, __typeinfo } from "rt/common";

13
std/assembly/rt/index.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
declare function __alloc(size: usize, id: u32): usize;
declare function __realloc(ref: usize, size: usize): usize;
declare function __free(ref: usize): void;
declare function __retain(ref: usize): void;
declare function __release(ref: usize): void;
declare function __retainRelease(ref: usize, oldRef: usize): usize;
declare function __collect(): void;
declare function __typeinfo(id: u32): u32;
declare function __instanceof(ref: usize, superId: u32): bool;
declare function __visit(ref: usize, cookie: i32): void;
declare function __visit_globals(cookie: u32): void;
declare function __visit_members(ref: usize, cookie: u32): void;
declare function __allocArray(length: i32, alignLog2: usize, id: u32, data?: usize): usize;

View File

@ -1,58 +0,0 @@
import { AL_MASK, DEBUG } from "./common";
//////////////////////////////////// Memory manager interface /////////////////////////////////////
import { ROOT, Block, BLOCK_OVERHEAD, initializeRoot, allocateBlock, reallocateBlock, freeBlock } from "./tlsf";
// @ts-ignore: decorator
@global @unsafe
export function __rt_allocate(size: usize, id: u32): usize {
var root = ROOT;
if (!root) {
initializeRoot();
root = ROOT;
}
var block = allocateBlock(root, size);
block.rtId = id;
return changetype<usize>(block) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_reallocate(ref: usize, size: usize): usize {
if (DEBUG) assert(ROOT); // must be initialized
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_free(ref: usize): void {
if (DEBUG) assert(ROOT); // must be initialized
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
freeBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD));
}
/////////////////////////////////// Garbage collector interface ///////////////////////////////////
import { increment, decrement, collectCycles } from "./pure";
// @ts-ignore: decorator
@global @unsafe
export function __rt_retain(ref: usize): void {
if (ref) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_release(ref: usize): void {
if (ref) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
export function __rt_collect(): void {
collectCycles();
}
export { __rt_typeinfo };

View File

@ -1,14 +1,10 @@
import { DEBUG } from "./common";
import { Block, freeBlock, ROOT } from "./tlsf";
import { DEBUG, BLOCK_OVERHEAD, BLOCK } from "rt/common";
import { Block, freeBlock, ROOT } from "rt/tlsf";
import { RTTIFlags } from "common/rtti";
/////////////////////////// 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│
@ -66,7 +62,9 @@ const ACYCLIC_FLAG: u32 = 0;
// @ts-ignore: decorator
@global
function __rt_visit(s: Block, cookie: i32): void {
function __visit(ref: usize, cookie: i32): void {
if (ref < HEAP_BASE) return;
var s = changetype<Block>(ref - BLOCK_OVERHEAD);
switch (cookie) {
case VISIT_DECREMENT: {
decrement(s);
@ -100,18 +98,18 @@ function __rt_visit(s: Block, cookie: i32): void {
}
/** Increments the reference count of the specified block by one.*/
export function increment(s: Block): void {
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 {
function decrement(s: Block): void {
var info = s.gcInfo;
var rc = info & REFCOUNT_MASK;
if (rc == 1) {
__rt_visit_members(s, VISIT_DECREMENT);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_DECREMENT);
if (!(info & BUFFERED_MASK)) {
freeBlock(ROOT, s);
} else {
@ -119,7 +117,7 @@ export function decrement(s: Block): void {
}
} else {
if (DEBUG) assert(rc > 0);
if (!(__rt_flags(s.rtId) & ACYCLIC_FLAG)) {
if (!(__typeinfo(s.rtId) & RTTIFlags.ACYCLIC)) {
s.gcInfo = BUFFERED_MASK | COLOR_PURPLE | (rc - 1);
if (!(info & BUFFERED_MASK)) {
appendRoot(s);
@ -164,7 +162,9 @@ function growRoots(): void {
}
/** Collects cyclic garbage. */
export function collectCycles(): void {
// @ts-ignore: decorator
@global @unsafe
export function __collect(): void {
// markRoots
var roots = ROOTS;
@ -205,7 +205,7 @@ 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);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_MARKGRAY);
}
}
@ -217,7 +217,7 @@ function scan(s: Block): void {
scanBlack(s);
} else {
s.gcInfo = (info & ~COLOR_MASK) | COLOR_WHITE;
__rt_visit_members(s, VISIT_SCAN);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCAN);
}
}
}
@ -225,7 +225,7 @@ function scan(s: Block): void {
/** 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);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_SCANBLACK);
}
/** Collects all white (member of a garbage cycle) nodes when completing the collection phase. */
@ -233,7 +233,31 @@ 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);
__visit_members(changetype<usize>(s) + BLOCK_OVERHEAD, VISIT_COLLECTWHITE);
}
freeBlock(ROOT, s);
}
// @ts-ignore: decorator
@global @unsafe
export function __retain(ref: usize): usize {
if (ref > HEAP_BASE) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
return ref;
}
// @ts-ignore: decorator
@global @unsafe
export function __release(ref: usize): void {
if (ref > HEAP_BASE) decrement(changetype<Block>(ref - BLOCK_OVERHEAD));
}
// @ts-ignore: decorator
@global @unsafe
export function __retainRelease(ref: usize, oldRef: usize): usize {
if (ref != oldRef) {
let heapBase = HEAP_BASE;
if (ref > heapBase) increment(changetype<Block>(ref - BLOCK_OVERHEAD));
if (oldRef > heapBase) decrement(changetype<Block>(oldRef - BLOCK_OVERHEAD));
}
return ref;
}

View File

@ -1,4 +1,4 @@
import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
import { AL_BITS, AL_MASK, DEBUG, BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "rt/common";
/////////////////////// The TLSF (Two-Level Segregate Fit) memory allocator ///////////////////////
// see: http://www.gii.upv.es/tlsf/
@ -69,7 +69,7 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
// │ if free: back ▲ │ ◄─┘
// └───────────────────────────────────────────────────────────────┘ payload ┘ >= MIN SIZE
// F: FREE, L: LEFTFREE
@unmanaged export class Block extends CommonBlock {
@unmanaged export class Block extends BLOCK {
/** Previous free block, if any. Only valid if free, otherwise part of payload. */
prev: Block | null;
@ -79,15 +79,13 @@ import { AL_BITS, AL_SIZE, AL_MASK, DEBUG, CommonBlock } from "./common";
// 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.
// Block constants. 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;
@inline const BLOCK_MINSIZE: usize = (3 * sizeof<usize>() + AL_MASK) & ~AL_MASK; // prev + next + back
// @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
// @inline const BLOCK_MAXSIZE: usize = 1 << (FL_BITS + SB_BITS - 1); // exclusive, lives in common.ts
/** Gets the left block of a block. Only valid if the left block is free. */
// @ts-ignore: decorator
@ -532,3 +530,32 @@ export function freeBlock(root: Root, block: Block): void {
block.mmInfo = blockInfo | FREE;
insertBlock(root, block);
}
// @ts-ignore: decorator
@global @unsafe
export function __alloc(size: usize, id: u32): usize {
var root = ROOT;
if (!root) {
initializeRoot();
root = ROOT;
}
var block = allocateBlock(root, size);
block.rtId = id;
return changetype<usize>(block) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
export function __realloc(ref: usize, size: usize): usize {
if (DEBUG) assert(ROOT); // must be initialized
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
return changetype<usize>(reallocateBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD), size)) + BLOCK_OVERHEAD;
}
// @ts-ignore: decorator
@global @unsafe
export function __free(ref: usize): void {
if (DEBUG) assert(ROOT); // must be initialized
assert(ref != 0 && !(ref & AL_MASK)); // must exist and be aligned
freeBlock(ROOT, changetype<Block>(ref - BLOCK_OVERHEAD));
}

View File

@ -1,148 +0,0 @@
// The runtime provides common functionality that links runtime interfaces for memory management
// and garbage collection to the standard library, making sure it all plays well together.
import { HEADER, HEADER_SIZE, allocate, register } from "./util/runtime";
import { E_NOTIMPLEMENTED } from "./util/error";
import { ArrayBufferView } from "./arraybuffer";
import { RTTIFlags, RTTIData } from "./common/rtti";
// @ts-ignore: decorator
@builtin
export declare const RTTI_BASE: usize;
/** Gets the computed unique id of a class type. */
// @ts-ignore: decorator
@builtin
export declare function __runtime_id<T>(): u32;
/** Marks root objects when a tracing GC is present. */
// @ts-ignore: decorator
@unsafe @builtin
export declare function __gc_mark_roots(): void;
/** Marks class members when a tracing GC is present. */
// @ts-ignore: decorator
@unsafe @builtin
export declare function __gc_mark_members(classId: u32, ref: usize): void;
/** Runtime implementation. */
@unmanaged
export class runtime {
private constructor() { return unreachable(); }
/** Determines whether a managed object is considered to be an instance of the class represented by the specified runtime id. */
static instanceof(ref: usize, superId: u32): bool { // keyword
var id = changetype<HEADER>(ref - HEADER_SIZE).classId;
var ptr = RTTI_BASE;
if (id && id <= load<u32>(ptr)) {
do if (id == superId) return true;
while (id = changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).base);
}
return false;
}
}
export namespace runtime {
/** Gets the runtime flags of the managed type represented by the specified runtime id. */
export function flags(id: u32): RTTIFlags {
var ptr = RTTI_BASE;
return !id || id > load<u32>(ptr)
? unreachable()
: changetype<RTTIData>(ptr + id * offsetof<RTTIData>()).flags;
}
/** Allocates and registers, but doesn't initialize the data of, a new managed object of the specified kind. */
// @ts-ignore: decorator
@unsafe
export function newObject(payloadSize: u32, id: u32): usize {
return register(allocate(<usize>payloadSize), id);
}
/** Allocates and registers, but doesn't initialize the data of, a new `String` of the specified length. */
// @ts-ignore: decorator
@unsafe
export function newString(length: i32): usize {
return newObject(length << 1, __runtime_id<String>());
}
/** Allocates and registers, but doesn't initialize the data of, a new `ArrayBuffer` of the specified byteLength. */
// @ts-ignore: decorator
@unsafe
export function newArrayBuffer(byteLength: i32): usize {
return newObject(byteLength, __runtime_id<ArrayBuffer>());
}
/** Allocates and registers a new `Array` of the specified kind using the given backing buffer. */
// @ts-ignore: decorator
@unsafe
export function newArray(id: u32, buffer: usize): usize {
var flags = runtime.flags(id); // traps if invalid
var alignLog2 = (<u32>flags / RTTIFlags.VALUE_ALIGN_0) & 31;
var byteLength: i32;
if (!buffer) buffer = newArrayBuffer(byteLength = 0);
else byteLength = changetype<ArrayBuffer>(buffer).byteLength;
var array = newObject(id, offsetof<i32[]>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = byteLength;
store<i32>(changetype<usize>(array), byteLength >>> alignLog2, offsetof<i32[]>("length_"));
if (flags & RTTIFlags.VALUE_MANAGED) {
let cur = buffer;
let end = cur + <usize>byteLength;
while (cur < end) {
let ref = load<usize>(cur);
if (ref) {
if (isDefined(__ref_link)) __ref_link(ref, array);
else if (isDefined(__ref_retain)) __ref_retain(ref);
else assert(false);
}
cur += sizeof<usize>();
}
}
return array;
}
/** Retains a managed object externally, making sure that it doesn't become collected. */
// @ts-ignore: decorator
@unsafe
export function retain(ref: usize): void {
if (isDefined(__ref_collect)) {
if (isDefined(__ref_link)) __ref_link(ref, changetype<usize>(ROOT));
else if (isDefined(__ref_retain)) __ref_retain(ref);
}
}
/** Releases a managed object externally, allowing it to become collected. */
// @ts-ignore: decorator
@unsafe
export function release(ref: usize): void {
if (isDefined(__ref_collect)) {
if (isDefined(__ref_unlink)) __ref_unlink(ref, changetype<usize>(ROOT));
else if (isDefined(__ref_release)) __ref_release(ref);
}
}
/** Performs a full garbage collection cycle. */
// @ts-ignore: decorator
@unsafe
export function collect(): void {
// FIXME: annotated unsafe because calling it in the middle of a function collects inner
// references prematurely with a tracing GC, which is pretty bad actually.
// function explode(): Ref {
// var ref = new Ref();
// gc.collect(); // collects ref
// return ref;
// }
if (isDefined(__ref_collect)) __ref_collect();
else throw new Error(E_NOTIMPLEMENTED);
}
}
class Root {}
/** A root object to retain managed objects on externally. */
// @ts-ignore
@lazy
var ROOT = new Root();

View File

@ -1,38 +0,0 @@
AssemblyScript runtimes
=======================
None
----
```
$> asc ... --runtime none
```
[No runtime](./none.ts) features at all. Useful for building low-level modules that do not require language features like managed classes, or if you'd like to compose your own runtime by including a custom memory allocator and garbage collector.
* No memory allocator
* No garbage collector
Trace
-----
```
$> asc ...
```
The [trace runtime](./trace.ts) adds support for dynamic memory management and garbage collection to your program.
* [TLSF memory allocator](../allocator/tlsf.ts)
* [ITCM garbage collector](../collector/itcm.ts)
Arena
-----
```
$> asc ... --runtime arena
```
The [arena runtime](./arena.ts) is just enough to make most language features work, but doesn't have sophisticated support for freeing memory. Useful when prototyping or for simple one-shot modules in that it produces very small modules with minimal overhead.
* [Arena memory allocator](../allocator/arena.ts) with `memory.reset()`
* No garbage collector

View File

@ -1,3 +0,0 @@
import "allocator/arena";
export { runtime as $ };

View File

@ -1,4 +0,0 @@
import "allocator/tlsf";
import "collector/itcm";
export { runtime as $ };

View File

@ -1,7 +1,6 @@
/// <reference path="./collector/index.d.ts" />
/// <reference path="./rt/index.d.ts" />
import { HASH } from "./util/hash";
import { __runtime_id, __gc_mark_members } from "./runtime";
// A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
@ -79,7 +78,7 @@ export class Set<K> {
}
private find(key: K, hashCode: u32): SetEntry<K> | null {
var entry = load<SetEntry<K>>(
var entry = load<SetEntry<K>>( // unmanaged!
changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE
);
while (entry) {
@ -106,23 +105,10 @@ export class Set<K> {
);
}
// append new entry
let entries = this.entries;
entry = changetype<SetEntry<K>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
entry.key = key;
// link with the set
if (isManaged<K>()) {
if (isNullable<K>()) {
if (key !== null) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
} else {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(key));
else assert(false);
}
}
entry = changetype<SetEntry<K>>(changetype<usize>(this.entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
entry.key = isManaged<K>()
? changetype<K>(__retain(changetype<usize>(key)))
: key;
++this.entriesCount;
// link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -132,24 +118,9 @@ export class Set<K> {
}
delete(key: K): bool {
var entry = this.find(key, HASH<K>(key));
var entry = this.find(key, HASH<K>(key)); // unmanaged!
if (!entry) return false;
if (isManaged<K>()) {
key = entry.key; // exact, e.g. string
if (isNullable<K>()) {
if (key !== null) {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
else assert(false);
}
} else {
if (isDefined(__ref_link)) {
if (isDefined(__ref_unlink)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
} else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
else assert(false);
}
}
if (isManaged<K>()) __release(changetype<usize>(entry.key)); // exact 'key'
entry.taggedNext |= EMPTY;
--this.entriesCount;
// check if rehashing is appropriate
@ -172,9 +143,9 @@ export class Set<K> {
var oldEnd = oldPtr + <usize>this.entriesOffset * ENTRY_SIZE<K>();
var newPtr = changetype<usize>(newEntries);
while (oldPtr != oldEnd) {
let oldEntry = changetype<SetEntry<K>>(oldPtr);
let oldEntry = changetype<SetEntry<K>>(oldPtr); // unmanaged!
if (!(oldEntry.taggedNext & EMPTY)) {
let newEntry = changetype<SetEntry<K>>(newPtr);
let newEntry = changetype<SetEntry<K>>(newPtr); // unmanaged!
newEntry.key = oldEntry.key;
let newBucketIndex = HASH<K>(oldEntry.key) & newBucketsMask;
let newBucketPtrBase = changetype<usize>(newBuckets) + <usize>newBucketIndex * BUCKET_SIZE;
@ -198,10 +169,10 @@ export class Set<K> {
// GC integration
@unsafe private __traverse(): void {
__ref_mark(changetype<usize>(this.buckets));
@unsafe private __traverse(cookie: u32): void {
__visit(changetype<usize>(this.buckets), cookie);
var entries = this.entries;
__ref_mark(changetype<usize>(entries));
__visit(changetype<usize>(entries), cookie);
if (isManaged<K>()) {
let cur = changetype<usize>(entries);
let end = cur + <usize>this.entriesOffset * ENTRY_SIZE<K>();
@ -211,12 +182,12 @@ export class Set<K> {
let val = changetype<usize>(entry.key);
if (isNullable<K>()) {
if (val) {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
} else {
__ref_mark(val);
__gc_mark_members(__runtime_id<K>(), val);
__visit(val, cookie);
__visit_members(val, cookie);
}
}
cur += ENTRY_SIZE<K>();

View File

@ -1,27 +1,26 @@
/// <reference path="./collector/index.d.ts" />
/// <reference path="./rt/index.d.ts" />
import { MAX_SIZE_32 } from "./util/allocator";
import { HEADER, HEADER_SIZE, allocate, register, NEWARRAY } from "./util/runtime";
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
import { E_INVALIDLENGTH } from "./util/error";
import { __runtime_id } from "./runtime";
import { ArrayBufferView } from "./arraybuffer";
import { idof } from "./builtins";
@sealed export abstract class String {
@lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof<u16>();
@lazy static readonly MAX_LENGTH: i32 = BLOCK_MAXSIZE >>> alignof<u16>();
// TODO Add and handle second argument
static fromCharCode(code: i32): String {
var out = allocate(2);
static fromCharCode(code: i32): string {
var out = __alloc(2, idof<string>());
store<u16>(out, <u16>code);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<string>(out); // retains
}
static fromCodePoint(code: i32): String {
static fromCodePoint(code: i32): string {
assert(<u32>code <= 0x10FFFF);
var sur = code > 0xFFFF;
var out = allocate((i32(sur) + 1) << 1);
var out = __alloc((i32(sur) + 1) << 1, idof<string>());
if (!sur) {
store<u16>(out, <u16>code);
} else {
@ -30,25 +29,19 @@ import { ArrayBufferView } from "./arraybuffer";
let lo: u32 = (code & 0x3FF) + 0xDC00;
store<u32>(out, (hi << 16) | lo);
}
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<string>(out); // retains
}
// @ts-ignore: decorator
// @unsafe
// constructor(length: i32) {
// return changetype<String>(register(allocate(<usize>length << 1), __runtime_id<String>()));
// }
get length(): i32 {
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >> 1;
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >> 1;
}
@operator("[]") charAt(pos: i32): String {
assert(this !== null);
if (<u32>pos >= <u32>this.length) return changetype<String>("");
var out = allocate(2);
var out = __alloc(2, idof<String>());
store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1)));
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
charCodeAt(pos: i32): i32 {
@ -75,10 +68,10 @@ import { ArrayBufferView } from "./arraybuffer";
var otherSize: isize = other.length << 1;
var outSize: usize = thisSize + otherSize;
if (outSize == 0) return changetype<String>("");
var out = allocate(outSize);
var out = __alloc(outSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize);
memory.copy(out + thisSize, changetype<usize>(other), otherSize);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool {
@ -194,9 +187,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (intStart < 0) intStart = max(size + intStart, 0);
var resultLength = min(max(end, 0), size - intStart);
if (resultLength <= 0) return changetype<String>("");
var out = allocate(resultLength << 1);
var out = __alloc(resultLength << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + intStart, resultLength);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
substring(start: i32, end: i32 = i32.MAX_VALUE): String {
@ -209,9 +202,9 @@ import { ArrayBufferView } from "./arraybuffer";
len = toPos - fromPos;
if (!len) return changetype<String>("");
if (!fromPos && toPos == this.length << 1) return this;
var out = allocate(len);
var out = __alloc(len, idof<String>());
memory.copy(out, changetype<usize>(this) + fromPos, len);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
trim(): String {
@ -237,9 +230,9 @@ import { ArrayBufferView } from "./arraybuffer";
}
if (!size) return changetype<String>("");
if (!start && size == length << 1) return this;
var out = allocate(size);
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
@inline
@ -267,9 +260,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (!offset) return this;
size -= offset;
if (!size) return changetype<String>("");
var out = allocate(size);
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
trimEnd(): String {
@ -286,9 +279,9 @@ import { ArrayBufferView } from "./arraybuffer";
}
if (!size) return changetype<String>("");
if (size == originalSize) return this;
var out = allocate(size);
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this), size);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
padStart(targetLength: i32, padString: string = " "): String {
@ -298,7 +291,7 @@ import { ArrayBufferView } from "./arraybuffer";
var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this;
var prependSize = targetSize - thisSize;
var out = allocate(targetSize);
var out = __alloc(targetSize, idof<String>());
if (prependSize > padSize) {
let repeatCount = (prependSize - 2) / padSize;
let restBase = repeatCount * padSize;
@ -309,7 +302,7 @@ import { ArrayBufferView } from "./arraybuffer";
memory.copy(out, changetype<usize>(padString), prependSize);
}
memory.copy(out + prependSize, changetype<usize>(this), thisSize);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
padEnd(targetLength: i32, padString: string = " "): String {
@ -319,7 +312,7 @@ import { ArrayBufferView } from "./arraybuffer";
var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this;
var appendSize = targetSize - thisSize;
var out = allocate(targetSize);
var out = __alloc(targetSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize);
if (appendSize > padSize) {
let repeatCount = (appendSize - 2) / padSize;
@ -330,7 +323,7 @@ import { ArrayBufferView } from "./arraybuffer";
} else {
memory.copy(out + thisSize, changetype<usize>(padString), appendSize);
}
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
repeat(count: i32 = 0): String {
@ -344,9 +337,9 @@ import { ArrayBufferView } from "./arraybuffer";
if (count == 0 || !length) return changetype<String>("");
if (count == 1) return this;
var out = allocate((length * count) << 1);
var out = __alloc((length * count) << 1, idof<String>());
memory.repeat(out, changetype<usize>(this), <usize>length << 1, count);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String {
@ -355,68 +348,64 @@ import { ArrayBufferView } from "./arraybuffer";
var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len);
len = end - begin;
if (len <= 0) return changetype<String>("");
var out = allocate(len << 1);
var out = __alloc(len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>begin << 1), <usize>len << 1);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
assert(this !== null);
if (!limit) return NEWARRAY<String>(0);
if (!limit) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
if (separator === null) return <String[]>[this];
var length: isize = this.length;
var sepLen: isize = separator.length;
if (limit < 0) limit = i32.MAX_VALUE;
if (!sepLen) {
if (!length) return NEWARRAY<String>(0);
if (!length) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
// split by chars
length = min<isize>(length, <isize>limit);
let result = NEWARRAY<String>(length);
let result = __allocArray(length, alignof<String>(), idof<Array<String>>());
let resultStart = changetype<ArrayBufferView>(result).dataStart;
for (let i: isize = 0; i < length; ++i) {
let charStr = allocate(2);
let charStr = __alloc(2, idof<String>());
store<u16>(charStr, load<u16>(changetype<usize>(this) + (<usize>i << 1)));
store<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
register(charStr, __runtime_id<String>());
if (isManaged<String>()) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(charStr), changetype<usize>(result));
if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(charStr));
}
if (isManaged<String>()) __retain(charStr);
}
return result;
return changetype<Array<String>>(result); // retains
} else if (!length) {
let result = NEWARRAY<String>(1);
store<string>(changetype<ArrayBufferView>(result).dataStart, ""); // no need to register/link
return result;
let result = __allocArray(1, alignof<String>(), idof<Array<String>>());
store<usize>(changetype<ArrayBufferView>(result).dataStart, changetype<usize>("")); // static ""
return changetype<Array<String>>(result); // retains
}
var result = NEWARRAY<String>(0);
var result = changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
var end = 0, start = 0, i = 0;
while ((end = this.indexOf(separator, start)) != -1) {
let len = end - start;
if (len > 0) {
let out = allocate(<usize>len << 1);
let out = __alloc(<usize>len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(changetype<String>(register(out, __runtime_id<String>())));
result.push(changetype<String>(out));
} else {
result.push(changetype<String>(""));
}
if (++i == limit) return result;
if (++i == limit) return changetype<Array<String>>(result); // retains
start = end + sepLen;
}
if (!start) {
let result = NEWARRAY<String>(1);
unchecked(result[0] = this);
return result;
if (!start) { // also means: loop above didn't do anything
result.push(this);
return changetype<Array<String>>(result); // retains
}
var len = length - start;
if (len > 0) {
let out = allocate(<usize>len << 1);
let out = __alloc(<usize>len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(changetype<String>(register(out, __runtime_id<String>())));
result.push(changetype<String>(out)); // retains
} else {
result.push(changetype<String>(""));
result.push(changetype<String>("")); // static ""
}
return result;
return changetype<Array<String>>(result); // retains
// releases result
}
toString(): String {
@ -484,10 +473,10 @@ import { ArrayBufferView } from "./arraybuffer";
}
}
assert(ptrPos == len);
var out = allocate(bufPos);
memory.copy(changetype<usize>(out), buf, bufPos);
var out = __alloc(bufPos, idof<String>());
memory.copy(out, buf, bufPos);
memory.free(buf);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
toUTF8(): usize {
@ -553,7 +542,7 @@ export function parseFloat(str: String): f64 {
var len: i32 = str.length;
if (!len) return NaN;
var ptr = changetype<usize>(str) /* + HEAD -> offset */;
var ptr = changetype<usize>(str);
var code = <i32>load<u16>(ptr);
// determine sign

View File

@ -1,7 +1,6 @@
import { allocate, register } from "./util/runtime";
import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort";
import { E_INDEXOUTOFRANGE } from "./util/error";
import { __runtime_id } from "./runtime";
import { idof } from "./builtins";
import { ArrayBufferView } from "./arraybuffer";
export class Int8Array extends ArrayBufferView {
@ -962,13 +961,11 @@ function SUBARRAY<TArray extends ArrayBufferView, T>(
else begin = min(begin, length);
if (end < 0) end = max(length + end, begin);
else end = max(min(end, length), begin);
var out = allocate(offsetof<TArray>());
var data = array.data;
var dataStart = array.dataStart;
changetype<ArrayBufferView>(out).data = data; // links
changetype<ArrayBufferView>(out).dataStart = dataStart + (<usize>begin << alignof<T>());
var out = __alloc(offsetof<TArray>(), idof<TArray>());
changetype<ArrayBufferView>(out).data = array.data; // retains
changetype<ArrayBufferView>(out).dataStart = array.dataStart + (<usize>begin << alignof<T>());
changetype<ArrayBufferView>(out).dataLength = (end - begin) << alignof<T>();
return changetype<TArray>(register(out, __runtime_id<TArray>()));
return changetype<TArray>(out); // retains
}
// @ts-ignore: decorator

View File

@ -1,19 +0,0 @@
/** Number of alignment bits. */
// @ts-ignore: decorator
@inline
export const AL_BITS: u32 = 3;
/** Number of possible alignment values. */
// @ts-ignore: decorator
@inline
export const AL_SIZE: usize = 1 << <usize>AL_BITS;
/** Mask to obtain just the alignment bits. */
// @ts-ignore: decorator
@inline
export const AL_MASK: usize = AL_SIZE - 1;
/** Maximum 32-bit allocation size. */
// @ts-ignore: decorator
@inline
export const MAX_SIZE_32: usize = 1 << 30; // 1GB

View File

@ -1,6 +1,7 @@
import { allocate, register, discard } from "./runtime";
/// <reference path="../rt/index.d.ts" />
import { idof } from "../builtins";
import { CharCode } from "./string";
import { __runtime_id } from "../runtime";
import { ArrayBufferView } from "../arraybuffer";
// @ts-ignore: decorator
@ -264,10 +265,10 @@ export function utoa32(value: u32): String {
if (!value) return "0";
var decimals = decimalCount32(value);
var out = allocate(decimals << 1);
var out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), value, decimals);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
export function itoa32(value: i32): String {
@ -277,12 +278,12 @@ export function itoa32(value: i32): String {
if (sign) value = -value;
var decimals = decimalCount32(value) + u32(sign);
var out = allocate(decimals << 1);
var out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), value, decimals);
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
export function utoa64(value: u64): String {
@ -292,14 +293,14 @@ export function utoa64(value: u64): String {
if (value <= u32.MAX_VALUE) {
let val32 = <u32>value;
let decimals = decimalCount32(val32);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa32_core(out, val32, decimals);
} else {
let decimals = decimalCount64(value);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa64_core(changetype<usize>(out), value, decimals);
}
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
export function itoa64(value: i64): String {
@ -312,16 +313,16 @@ export function itoa64(value: i64): String {
if (<u64>value <= <u64>u32.MAX_VALUE) {
let val32 = <u32>value;
let decimals = decimalCount32(val32) + u32(sign);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa32_core(changetype<usize>(out), val32, decimals);
} else {
let decimals = decimalCount64(value) + u32(sign);
out = allocate(decimals << 1);
out = __alloc(decimals << 1, idof<String>());
utoa64_core(changetype<usize>(out), value, decimals);
}
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
return changetype<String>(register(out, __runtime_id<String>()));
return changetype<String>(out); // retains
}
export function itoa<T extends number>(value: T): String {
@ -626,11 +627,12 @@ export function dtoa(value: f64): String {
if (isNaN<f64>(value)) return "NaN";
return select<String>("-Infinity", "Infinity", value < 0);
}
var temp = allocate(MAX_DOUBLE_LENGTH << 1);
var temp = __alloc(MAX_DOUBLE_LENGTH << 1, idof<String>());
var length = dtoa_core(temp, value);
var result = changetype<String>(temp).substring(0, length); // registers
discard(temp);
return result;
if (length < MAX_DOUBLE_LENGTH) {
return changetype<String>(temp).substring(0, length); // retains/releases `temp`, retains return
}
return changetype<String>(temp); // retains
}
export function itoa_stream<T extends number>(buffer: usize, offset: usize, value: T): u32 {
@ -692,8 +694,7 @@ export function dtoa_stream(buffer: usize, offset: usize, value: f64): u32 {
} else {
let sign = i32(value < 0);
let len = 8 + sign;
let source = changetype<usize>(select<String>("-Infinity", "Infinity", sign));
memory.copy(buffer, source, len << 1);
memory.copy(buffer, changetype<usize>(select<String>("-Infinity", "Infinity", sign)), len << 1);
return len;
}
}

View File

@ -1,158 +0,0 @@
import { AL_MASK, MAX_SIZE_32 } from "./allocator";
import { __runtime_id } from "../runtime";
import { Array } from "../array";
import { ArrayBufferView } from "../arraybuffer";
/**
* The common runtime object header prepended to all managed objects. Has a size of 16 bytes in
* WASM32 and contains a classId (e.g. for instanceof checks), the allocation size (e.g. for
* .byteLength and .length computation) and additional reserved fields to be used by GC. If no
* GC is present, the HEADER is cut into half excluding the reserved fields, as indicated by
* HEADER_SIZE.
*/
@unmanaged export class HEADER {
/** Unique id of the respective class or a magic value if not yet registered.*/
classId: u32;
/** Size of the allocated payload. */
payloadSize: u32;
/** Reserved field for use by GC. */
reserved1: usize; // itcm: tagged next
/** Reserved field for use by GC. */
reserved2: usize; // itcm: prev
}
/** Common runtime header size. */
// @ts-ignore: decorator
@lazy
export const HEADER_SIZE: usize = (offsetof<HEADER>() + AL_MASK) & ~AL_MASK;
/** Common runtime header magic. Used to assert registered/unregistered status. */
// @ts-ignore: decorator
@lazy
export const HEADER_MAGIC: u32 = 0xA55E4B17;
/** Maximum byte length of any buffer-like object. */
// @ts-ignore
@lazy
export const MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE;
/** Adjusts an allocation to actual block size. Primarily targets TLSF. */
export function adjust(payloadSize: usize): usize {
// round up to power of 2, e.g. with HEADER_SIZE=8:
// 0 -> 2^3 = 8
// 1..8 -> 2^4 = 16
// 9..24 -> 2^5 = 32
// ...
// MAX_LENGTH -> 2^30 = 0x40000000 (MAX_SIZE_32)
return <usize>1 << <usize>(<u32>32 - clz<u32>(payloadSize + HEADER_SIZE - 1));
}
/** Allocates the memory necessary to represent a managed object of the specified size. */
// @ts-ignore: decorator
@unsafe
export function allocate(payloadSize: usize): usize {
var header = changetype<HEADER>(memory.allocate(adjust(payloadSize)));
header.classId = HEADER_MAGIC;
header.payloadSize = payloadSize;
if (isDefined(__ref_collect)) {
header.reserved1 = 0;
header.reserved2 = 0;
}
return changetype<usize>(header) + HEADER_SIZE;
}
/** Reallocates the memory of a managed object that turned out to be too small or too large. */
// @ts-ignore: decorator
@unsafe
export function reallocate(ref: usize, newPayloadSize: usize): usize {
// Background: When managed objects are allocated these aren't immediately registered with GC
// but can be used as scratch objects while unregistered. This is useful in situations where
// the object must be reallocated multiple times because its final size isn't known beforehand,
// e.g. in Array#filter, with only the final object making it into GC'ed userland.
var header = changetype<HEADER>(ref - HEADER_SIZE);
var payloadSize = header.payloadSize;
if (payloadSize < newPayloadSize) {
let newAdjustedSize = adjust(newPayloadSize);
if (select(adjust(payloadSize), 0, ref > HEAP_BASE) < newAdjustedSize) {
// move if the allocation isn't large enough or not a heap object
let newHeader = changetype<HEADER>(memory.allocate(newAdjustedSize));
newHeader.classId = header.classId;
if (isDefined(__ref_collect)) {
newHeader.reserved1 = 0;
newHeader.reserved2 = 0;
}
let newRef = changetype<usize>(newHeader) + HEADER_SIZE;
memory.copy(newRef, ref, payloadSize);
memory.fill(newRef + payloadSize, 0, newPayloadSize - payloadSize);
if (header.classId == HEADER_MAGIC) {
// free right away if not registered yet
assert(ref > HEAP_BASE); // static objects aren't scratch objects
memory.free(changetype<usize>(header));
} else if (isDefined(__ref_collect)) {
// if previously registered, register again
// @ts-ignore: stub
__ref_register(ref);
}
header = newHeader;
ref = newRef;
} else {
// otherwise just clear additional memory within this block
memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize);
}
} else {
// if the size is the same or less, just update the header accordingly.
// unused space is cleared when grown, so no need to do this here.
}
header.payloadSize = newPayloadSize;
return ref;
}
/** Discards the memory of a managed object that hasn't been registered yet. */
// @ts-ignore: decorator
@unsafe
export function discard(ref: usize): void {
if (!ASC_NO_ASSERT) {
assert(ref > HEAP_BASE); // must be a heap object
let header = changetype<HEADER>(ref - HEADER_SIZE);
assert(header.classId == HEADER_MAGIC);
memory.free(changetype<usize>(header));
} else {
memory.free(changetype<usize>(ref - HEADER_SIZE));
}
}
/** Registers a managed object of the kind represented by the specified runtime id. */
// @ts-ignore: decorator
@unsafe
export function register(ref: usize, id: u32): usize {
if (!ASC_NO_ASSERT) {
assert(ref > HEAP_BASE); // must be a heap object
let header = changetype<HEADER>(ref - HEADER_SIZE);
assert(header.classId == HEADER_MAGIC);
header.classId = id;
} else {
changetype<HEADER>(ref - HEADER_SIZE).classId = id;
}
if (isDefined(__ref_register)) __ref_register(ref);
return ref;
}
// @ts-ignore: decorator
@unsafe
export function makeArray(length: i32, alignLog2: usize, id: u32, data: usize = 0): usize {
var array = register(allocate(offsetof<i32[]>()), id);
var bufferSize = <usize>length << alignLog2;
var buffer = register(allocate(bufferSize), __runtime_id<ArrayBuffer>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
store<i32>(changetype<usize>(array), length, offsetof<i32[]>("length_"));
if (data) memory.copy(buffer, data, bufferSize);
return array;
}
// @ts-ignore: decorator
@inline
export function NEWARRAY<T>(length: i32): Array<T> {
return changetype<Array<T>>(makeArray(length, alignof<T>(), __runtime_id<Array<T>>(), 0));
}