use gc interface directly, document

This commit is contained in:
dcode
2019-03-26 23:35:08 +01:00
parent 7c0dc66849
commit 3146f8f9e0
95 changed files with 17360 additions and 13504 deletions

View File

@ -1,7 +1,6 @@
import {
ALLOCATE, REALLOCATE, DISCARD, RETAIN, RELEASE, REGISTER, MAX_BYTELENGTH, MOVE, MAKEARRAY,
ArrayBufferView
} from "./runtime";
/// <reference path="./collector/index.d.ts" />
import { ALLOCATE, REALLOCATE, DISCARD, REGISTER, MAX_BYTELENGTH, MAKEARRAY, ArrayBufferView } from "./runtime";
import { ArrayBuffer } from "./arraybuffer";
import { COMPARATOR, SORT } from "./util/sort";
import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number";
@ -90,19 +89,21 @@ export class Array<T> extends ArrayBufferView {
return -1;
}
@operator("[]") // unchecked is built-in
private __get(index: i32): T {
@operator("[]") private __get(index: i32): T {
if (isReference<T>()) {
if (!isNullable<T>()) {
if (<u32>index >= <u32>this.length_) throw new Error(E_HOLEYARRAY);
}
}
if (<u32>index >= <u32>this.dataLength >>> alignof<T>()) throw new RangeError(E_INDEXOUTOFRANGE);
return this.__unchecked_get(index);
}
@operator("{}") private __unchecked_get(index: i32): T {
return load<T>(this.dataStart + (<usize>index << alignof<T>()));
}
@operator("[]=") // unchecked is built-in
private __set(index: i32, value: T): void {
@operator("[]=") private __set(index: i32, value: T): void {
var length = this.length_;
if (isReference<T>()) {
if (!isNullable<T>()) {
@ -110,21 +111,37 @@ export class Array<T> extends ArrayBufferView {
}
}
ensureCapacity(this, index + 1, alignof<T>());
this.__unchecked_set(index, value);
if (index >= length) this.length_ = index + 1;
}
@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>()) {
RELEASE<T,this>(oldValue, this); // handles != null
} else if (oldValue !== null) {
RELEASE<T,this>(oldValue, this); // requires != null
if (isDefined(__ref_link)) {
if (oldValue !== null) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
if (value !== null) __ref_link(changetype<usize>(value), changetype<usize>(this));
} else if (__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 (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<T>(offset, RETAIN<T,this>(value, this));
}
} else {
store<T>(this.dataStart + (<usize>index << alignof<T>()), value);
}
if (index >= length) this.length_ = index + 1;
}
fill(value: T, start: i32 = 0, end: i32 = i32.MAX_VALUE): this {
@ -177,15 +194,37 @@ export class Array<T> extends ArrayBufferView {
return -1;
}
push(element: T): i32 {
var newLength = this.length_ + 1;
push(value: T): i32 {
var length = this.length_;
var newLength = length + 1;
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 (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 (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);
}
}
} else {
store<T>(this.dataStart + (<usize>length << alignof<T>()), value);
}
this.length_ = newLength;
store<T>(this.dataStart + (<usize>(newLength - 1) << alignof<T>()),
isManaged<T>()
? RETAIN<T,this>(element, this)
: element
);
return newLength;
}
@ -198,13 +237,37 @@ export class Array<T> extends ArrayBufferView {
if (isManaged<T>()) {
let thisStart = this.dataStart;
for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) {
store<T>(outStart + offset, RETAIN<T,Array<T>>(load<T>(thisStart + offset), out));
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);
}
}
outStart += thisSize;
let otherStart = other.dataStart;
let otherSize = <usize>otherLen << alignof<T>();
for (let offset: usize = 0; offset < otherSize; offset += sizeof<T>()) {
let element = load<T>(otherStart + offset);
store<T>(outStart + thisSize + offset, RETAIN<T,Array<T>>(element, out));
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);
}
}
} else {
memory.copy(outStart, this.dataStart, thisSize);
@ -260,12 +323,23 @@ export class Array<T> extends ArrayBufferView {
var outStart = 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(value, index, this);
store<U>(outStart + (<usize>index << alignof<U>()),
isManaged<U>()
? RETAIN<U,Array<U>>(result, out)
: result
);
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);
}
} else {
store<U>(outStart + (<usize>index << alignof<U>()), callbackfn(value, index, this));
}
}
return out;
}
@ -330,17 +404,26 @@ export class Array<T> extends ArrayBufferView {
unshift(element: T): i32 {
var newLength = this.length_ + 1;
ensureCapacity(this, newLength, alignof<T>());
var base = this.dataStart;
var dataStart = this.dataStart;
memory.copy(
base + sizeof<T>(),
base,
dataStart + sizeof<T>(),
dataStart,
<usize>(newLength - 1) << alignof<T>()
);
store<T>(base,
isManaged<T>()
? RETAIN<T,this>(element, this)
: element
);
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);
}
}
this.length_ = newLength;
return newLength;
}
@ -353,14 +436,27 @@ export class Array<T> extends ArrayBufferView {
var slice = MAKEARRAY<T>(length);
var sliceBase = slice.dataStart;
var thisBase = this.dataStart + (<usize>begin << alignof<T>());
for (let i = 0; i < length; ++i) {
let offset = <usize>i << alignof<T>();
let element = load<T>(thisBase + offset);
store<T>(sliceBase + offset,
isManaged<T>()
? RETAIN<T,Array<T>>(element, slice)
: element
);
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);
}
off += sizeof<usize>();
}
} else {
memory.copy(sliceBase, thisBase, length << alignof<T>());
}
return slice;
}
@ -373,18 +469,29 @@ export class Array<T> extends ArrayBufferView {
var resultStart = result.dataStart;
var thisStart = this.dataStart;
var thisBase = thisStart + (<usize>start << alignof<T>());
for (let i = 0; i < deleteCount; ++i) {
store<T>(resultStart + (<usize>i << alignof<T>()),
isManaged<T>()
? MOVE<T,this,Array<T>>(load<T>(thisBase + (<usize>i << alignof<T>())), this, result)
: load<T>(thisBase + (<usize>i << 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) {
__ref_unlink(ref, changetype<usize>(this));
__ref_link(ref, changetype<usize>(result));
}
} else {
__ref_unlink(ref, changetype<usize>(this));
__ref_link(ref, changetype<usize>(result));
}
}
}
} else {
memory.copy(
result.dataStart,
thisBase,
<usize>deleteCount << alignof<T>()
);
}
memory.copy(
result.dataStart,
thisBase,
<usize>deleteCount << alignof<T>()
);
var offset = start + deleteCount;
if (length != offset) {
memory.copy(

View File

@ -0,0 +1,107 @@
Garbage collector interface
===========================
A garbage collector for AssemblyScript must implement the common and either of the tracing or reference counting interfaces.
Common
------
* **__ref_collect**()<br />
Triggers a full garbage collection cycle.
Tracing
-------
* **__ref_register**(ref: `usize`)<br />
Sets up a new reference.
* **__ref_link**(ref: `usize`, parentRef: `usize`)<br />
Links a reference to a parent that is now referencing it.
* **__ref_unlink**(ref: `usize`, parentRef: `usize`)<br />
Unlinks a reference from a parent that was referencing it.
Reference counting
------------------
* **__ref_retain**(ref: `usize`)<br />
Retains a reference, usually incrementing RC.
* **__ref_release**(ref: `usize`)<br />
Releases a reference, usually decrementing RC.
Reference counting may also implement `__ref_register` if necessary.
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 (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)) {
__ref_unlink(oldRef, parentRef);
__ref_link(ref, parentRef);
} else if (isDefined(__ref_retain)) {
__ref_release(oldRef);
__ref_retain(ref);
} else assert(false);
}
}
```
### Deletion
```ts
if (isNullable<T>()) {
if (ref) {
if (isDefined(__ref_link)) __ref_unlink(ref, parentRef);
else if (isDefined(__ref_retain)) __ref_release(ref);
else assert(false);
}
} else {
if (isDefined(__ref_link)) __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

@ -6,30 +6,40 @@ const TRACE = false;
// @ts-ignore: decorator
@global @unsafe
function __gc_register(ref: usize): void {
if (TRACE) trace("gc.register", 1, ref);
function __ref_register(ref: usize): void {
if (TRACE) trace("dummy.register", 1, ref);
}
// @ts-ignore: decorator
@global @unsafe
function __gc_retain(ref: usize, parentRef: usize): void {
if (TRACE) trace("gc.retain", 2, ref, parentRef);
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 __gc_release(ref: usize, parentRef: usize): void {
if (TRACE) trace("gc.release", 2, ref, parentRef);
function __ref_unlink(ref: usize, parentRef: usize): void {
if (TRACE) trace("dummy.unlink", 2, ref, parentRef);
}
// @ts-ignore: decorator
@global @unsafe
function __gc_move(ref: usize, oldParentRef: usize, newParentRef: usize): void {
if (TRACE) trace("gc.move", 3, ref, oldParentRef, newParentRef);
}
// Reference counting
// @ts-ignore: decorator
@global @unsafe
function __gc_collect(): void {
if (TRACE) trace("gc.collect");
}
// // @ts-ignore: decorator
// @global @unsafe
// function __ref_retain(ref: usize): void {
// if (TRACE) trace("dummy.retain", 1, ref);
// }
// // @ts-ignore: decorator
// @global @unsafe
// function __ref_release(ref: usize): void {
// if (TRACE) trace("dummy.release", 1, ref);
// }

11
std/assembly/collector/index.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
// common
declare function __ref_collect(): void;
// tracing
declare function __ref_register(ref: usize): void;
declare function __ref_link(ref: usize, parentRef: usize): void;
declare function __ref_unlink(ref: usize, parentRef: usize): void;
// reference counting
declare function __ref_retain(ref: usize): void;
declare function __ref_release(ref: usize): void;

View File

@ -211,34 +211,12 @@ function objToRef(obj: ManagedObject): usize {
return changetype<usize>(obj) + HEADER_SIZE;
}
// @ts-ignore: decorator
@global @unsafe
export function __gc_register(ref: usize): void {
if (TRACE) trace("gc.register", 1, ref);
step(); // also makes sure it's initialized
var obj = refToObj(ref);
obj.color = white;
fromSpace.push(obj);
}
// Garbage collector interface
// @ts-ignore: decorator
@global @unsafe
export function __gc_retain(ref: usize, parentRef: usize): void {
if (TRACE) trace("gc.retain", 2, ref, parentRef);
var parent = refToObj(parentRef);
if (parent.color == i32(!white) && refToObj(ref).color == white) parent.makeGray();
}
// @ts-ignore: decorator
@global @unsafe
export function __gc_release(ref: usize, parentRef: usize): void {
if (TRACE) trace("gc.release", 2, ref, parentRef);
}
// @ts-ignore: decorator
@global @unsafe
export function __gc_collect(): void {
if (TRACE) trace("gc.collect");
export function __ref_collect(): void {
if (TRACE) trace("itcm.collect");
// begin collecting if not yet collecting
switch (state) {
case State.INIT:
@ -247,3 +225,27 @@ export function __gc_collect(): void {
// finish the cycle
while (state != State.IDLE) step();
}
// @ts-ignore: decorator
@global @unsafe
export function __ref_register(ref: usize): void {
if (TRACE) trace("itcm.register", 1, ref);
step(); // also makes sure it's initialized
var obj = refToObj(ref);
obj.color = white;
fromSpace.push(obj);
}
// @ts-ignore: decorator
@global @unsafe
export function __ref_link(ref: usize, parentRef: usize): void {
if (TRACE) trace("itcm.link", 2, ref, parentRef);
var parent = refToObj(parentRef);
if (parent.color == i32(!white) && refToObj(ref).color == white) parent.makeGray();
}
// @ts-ignore: decorator
@global @unsafe
export function __ref_unlink(ref: usize, parentRef: usize): void {
if (TRACE) trace("itcm.unlink", 2, ref, parentRef);
}

View File

@ -235,23 +235,23 @@ function collectWhite(s: Header): void {
// Garbage collector interface
// @ts-ignore: decorator
@global
function __gc_link(ref: usize, parentRef: usize): void {
@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
function __gc_unlink(ref: usize, parentRef: usize): void {
@global @unsafe
function __ref_release(ref: usize): void {
decrement(changetype<Header>(ref - HEADER_SIZE))
}
// @ts-ignore: decorator
@global
function __gc_collect(): void {
collectCycles();
}
// TODO:
// A significant constant-factor improvement can be obtained for cycle collection by observing that

View File

@ -1,4 +1,4 @@
import { ALLOCATE, REGISTER, MAX_BYTELENGTH, HEADER, HEADER_SIZE, RETAIN, RELEASE } from "./runtime";
import { ALLOCATE, REGISTER, MAX_BYTELENGTH, HEADER, HEADER_SIZE } from "./runtime";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_HOLEYARRAY } from "./util/error";
// NOTE: DO NOT USE YET!
@ -45,8 +45,23 @@ export class FixedArray<T> {
let offset = changetype<usize>(this) + (<usize>index << alignof<T>());
let oldValue = load<T>(offset);
if (value !== oldValue) {
RELEASE<T,this>(oldValue, this);
store<T>(offset, RETAIN<T,this>(value, this));
store<T>(offset, value);
if (oldValue !== null) {
if (isDefined(__ref_link)) __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);
}
}
} else {
store<T>(changetype<usize>(this) + (<usize>index << alignof<T>()), value);

View File

@ -1,47 +1,16 @@
/// <reference path="./collector/index.d.ts" />
/** Garbage collector interface. */
export namespace gc {
/** Whether the garbage collector interface is implemented. */
// @ts-ignore: decorator
@lazy
export const IMPLEMENTED: bool = isDefined(
// @ts-ignore: stub
__gc_register
);
/** Registers a managed object to be tracked by the garbage collector. */
// @ts-ignore: decorator
@unsafe @inline
export function register(ref: usize): void {
// @ts-ignore: stub
if (isDefined(__gc_register)) __gc_register(ref);
else WARNING("missing implementation: gc.register");
}
/** Links a registered object with the registered object now referencing it. */
// @ts-ignore: decorator
@unsafe @inline
export function link(ref: usize, parentRef: usize): void {
// @ts-ignore: stub
if (isDefined(__gc_link)) __gc_link(ref, parentRef);
else WARNING("missing implementation: gc.link");
}
/** Marks an object as being reachable. */
// @ts-ignore: decorator
@unsafe
export function mark(ref: usize): void {
// @ts-ignore: stub
if (isDefined(__gc_mark)) __gc_mark(ref);
else WARNING("missing implementation: gc.mark");
}
export const IMPLEMENTED: bool = isDefined(__ref_collect);
/** Performs a full garbage collection cycle. */
export function collect(): void {
// @ts-ignore: stub
if (isDefined(__gc_collect)) __gc_collect();
if (isDefined(__ref_collect)) __ref_collect();
else WARNING("missing implementation: gc.collect");
}
}
// TODO: move marking into userspace using builtins like iterateFields?

View File

@ -1,4 +1,5 @@
import { RETAIN, RELEASE, HEADER } from "./runtime";
/// <reference path="./collector/index.d.ts" />
import { HASH } from "./util/hash";
// A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
@ -107,8 +108,27 @@ export class Map<K,V> {
if (isManaged<V>()) {
let oldValue = entry.value;
if (value !== oldValue) {
RELEASE<V,this>(oldValue, this);
entry.value = RETAIN<V,this>(value, this);
entry.value = value;
if (isNullable<V>()) {
if (oldValue !== null) {
if (isDefined(__ref_link)) __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)) {
__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;
@ -124,12 +144,36 @@ 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 = changetype<MapEntry<K,V>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K,V>());
entry.key = key;
entry.value = value;
// link with the map
entry.key = isManaged<K>() ? RETAIN<K,this>(key, this) : key;
entry.value = isManaged<V>() ? RETAIN<V,this>(value, this) : value;
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);
}
}
++this.entriesCount;
// link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -141,8 +185,34 @@ export class Map<K,V> {
delete(key: K): bool {
var entry = this.find(key, HASH<K>(key));
if (!entry) return false;
if (isManaged<K>()) RELEASE<K,this>(entry.key, this);
if (isManaged<V>()) RELEASE<V,this>(entry.value, this);
if (isManaged<K>()) {
let oldKey = entry.key;
if (isNullable<K>()) {
if (oldKey !== null) {
if (isDefined(__ref_link)) __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)) __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)) __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)) __ref_unlink(changetype<usize>(oldValue), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(oldValue));
else assert(false);
}
}
entry.taggedNext |= EMPTY;
--this.entriesCount;
// check if rehashing is appropriate

View File

@ -10,14 +10,6 @@ import { AL_MASK, MAX_SIZE_32 } from "./util/allocator";
import { HEAP_BASE, memory } from "./memory";
import { Array } from "./array";
/** Whether the memory manager interface is implemented. */
// @ts-ignore: decorator, stub
@lazy export const MM_IMPLEMENTED: bool = isDefined(__memory_allocate);
/** Whether the garbage collector interface is implemented. */
// @ts-ignore: decorator, stub
@lazy export const GC_IMPLEMENTED: bool = isDefined(__gc_register);
/**
* 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
@ -31,15 +23,15 @@ import { Array } from "./array";
/** Size of the allocated payload. */
payloadSize: u32;
/** Reserved field for use by GC. Only present if GC is. */
gc1: usize; // itcm: tagged next
reserved1: usize; // itcm: tagged next
/** Reserved field for use by GC. Only present if GC is. */
gc2: usize; // itcm: prev
reserved2: usize; // itcm: prev
}
/** Common runtime header size. */
export const HEADER_SIZE: usize = GC_IMPLEMENTED
? (offsetof<HEADER>( ) + AL_MASK) & ~AL_MASK // full header if GC is present
: (offsetof<HEADER>("gc1") + AL_MASK) & ~AL_MASK; // half header if GC is absent
export const HEADER_SIZE: usize = isDefined(__ref_collect)
? (offsetof<HEADER>( ) + AL_MASK) & ~AL_MASK // full header if GC is present
: (offsetof<HEADER>("reserved1") + AL_MASK) & ~AL_MASK; // half header if GC is absent
/** Common runtime header magic. Used to assert registered/unregistered status. */
export const HEADER_MAGIC: u32 = 0xA55E4B17;
@ -72,16 +64,16 @@ export function ADJUSTOBLOCK(payloadSize: usize): usize {
// @ts-ignore: decorator
@unsafe @inline
export function ALLOCATE(payloadSize: usize): usize {
return doAllocate(payloadSize);
return allocate(payloadSize);
}
function doAllocate(payloadSize: usize): usize {
function allocate(payloadSize: usize): usize {
var header = changetype<HEADER>(memory.allocate(ADJUSTOBLOCK(payloadSize)));
header.classId = HEADER_MAGIC;
header.payloadSize = payloadSize;
if (GC_IMPLEMENTED) {
header.gc1 = 0;
header.gc2 = 0;
if (isDefined(__ref_collect)) {
header.reserved1 = 0;
header.reserved2 = 0;
}
return changetype<usize>(header) + HEADER_SIZE;
}
@ -105,10 +97,10 @@ export function ALLOCATE_UNMANAGED(size: usize): usize {
// @ts-ignore: decorator
@unsafe @inline
export function REALLOCATE(ref: usize, newPayloadSize: usize): usize {
return doReallocate(ref, newPayloadSize);
return reallocate(ref, newPayloadSize);
}
function doReallocate(ref: usize, newPayloadSize: usize): usize {
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,
@ -121,9 +113,9 @@ function doReallocate(ref: usize, newPayloadSize: usize): usize {
// 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 (GC_IMPLEMENTED) {
newHeader.gc1 = 0;
newHeader.gc2 = 0;
if (isDefined(__ref_collect)) {
newHeader.reserved1 = 0;
newHeader.reserved2 = 0;
}
let newRef = changetype<usize>(newHeader) + HEADER_SIZE;
memory.copy(newRef, ref, payloadSize);
@ -132,10 +124,10 @@ function doReallocate(ref: usize, newPayloadSize: usize): usize {
// free right away if not registered yet
assert(ref > HEAP_BASE); // static objects aren't scratch objects
memory.free(changetype<usize>(header));
} else if (GC_IMPLEMENTED) {
} else if (isDefined(__ref_collect)) {
// if previously registered, register again
// @ts-ignore: stub
__gc_register(ref);
__ref_register(ref);
}
header = newHeader;
ref = newRef;
@ -161,112 +153,23 @@ function doReallocate(ref: usize, newPayloadSize: usize): usize {
@unsafe @inline
export function REGISTER<T>(ref: usize): T {
if (!isReference<T>()) ERROR("reference expected");
return changetype<T>(doRegister(ref, CLASSID<T>()));
return changetype<T>(register(ref, CLASSID<T>()));
}
function doRegister(ref: usize, classId: u32): usize {
if (!ASC_NO_ASSERT) assertUnregistered(ref);
changetype<HEADER>(ref - HEADER_SIZE).classId = classId;
// @ts-ignore: stub
if (GC_IMPLEMENTED) __gc_register(ref);
return ref;
}
/**
* Introduces a new reference to ref hold by parentRef. A tracing garbage collector will most
* likely link the runtime object within its internal graph when RETAIN is called, while a
* reference counting collector will increment the reference count. If a reference is moved
* from one parent to another, use MOVE instead.
*/
// @ts-ignore: decorator
@unsafe @inline
export function RETAIN<T,TParent>(ref: T, parentRef: TParent): T {
if (!isManaged<T>()) ERROR("managed reference expected");
if (!isManaged<TParent>()) ERROR("managed reference expected");
if (isNullable<T>()) {
if (ref !== null) doRetain(changetype<usize>(ref), changetype<usize>(parentRef));
} else {
doRetain(changetype<usize>(ref), changetype<usize>(parentRef));
}
return ref;
}
function doRetain(ref: usize, parentRef: usize): void {
function register(ref: usize, classId: u32): usize {
if (!ASC_NO_ASSERT) {
assertRegistered(ref);
assertRegistered(parentRef);
assert(ref > HEAP_BASE); // must be a heap object
let header = changetype<HEADER>(ref - HEADER_SIZE);
assert(header.classId == HEADER_MAGIC);
header.classId = classId;
} else {
changetype<HEADER>(ref - HEADER_SIZE).classId = classId;
}
// @ts-ignore: stub
if (GC_IMPLEMENTED) __gc_retain(changetype<usize>(ref), changetype<usize>(parentRef));
}
/**
* Releases a reference to ref hold by parentRef. A tracing garbage collector will most likely
* ignore this by design, while a reference counting collector decrements the reference count
* and potentially frees the runtime object.
*/
// @ts-ignore: decorator
@unsafe @inline
export function RELEASE<T,TParent>(ref: T, parentRef: TParent): void {
if (!isManaged<T>()) ERROR("managed reference expected");
if (!isManaged<TParent>()) ERROR("managed reference expected");
if (isNullable<T>()) {
if (ref !== null) doRelease(changetype<usize>(ref), changetype<usize>(parentRef));
} else {
doRelease(changetype<usize>(ref), changetype<usize>(parentRef));
}
}
function doRelease(ref: usize, parentRef: usize): void {
if (!ASC_NO_ASSERT) {
assertRegistered(ref);
assertRegistered(parentRef);
}
// @ts-ignore: stub
if (GC_IMPLEMENTED) __gc_release(changetype<usize>(ref), changetype<usize>(parentRef));
}
/**
* Moves a reference to ref hold by oldParentRef to be now hold by newParentRef. This is a
* special case of first RELEASE'ing a reference on one and instantly RETAIN'ing the reference
* on another parent. A tracing garbage collector will most likely link the runtime object as if
* RETAIN'ed on the new parent only, while a reference counting collector can skip increment and
* decrement, as decrementing might otherwise involve a costly check for cyclic garbage.
*/
// @ts-ignore: decorator
@unsafe @inline
export function MOVE<T,TOldParent,TNewParent>(ref: T, oldParentRef: TOldParent, newParentRef: TNewParent): T {
if (!isManaged<T>()) ERROR("managed reference expected");
if (!isManaged<TOldParent>()) ERROR("managed reference expected");
if (!isManaged<TNewParent>()) ERROR("managed reference expected");
if (isNullable<T>()) {
if (ref !== null) doMove(changetype<usize>(ref), changetype<usize>(oldParentRef), changetype<usize>(newParentRef));
} else {
doMove(changetype<usize>(ref), changetype<usize>(oldParentRef), changetype<usize>(newParentRef));
}
if (isDefined(__ref_register)) __ref_register(ref);
return ref;
}
function doMove(ref: usize, oldParentRef: usize, newParentRef: usize): void {
if (!ASC_NO_ASSERT) {
assertRegistered(ref);
assertRegistered(oldParentRef);
assertRegistered(newParentRef);
}
if (GC_IMPLEMENTED) {
// @ts-ignore: stub
if (isDefined(__gc_move)) {
// @ts-ignore: stub
__gc_move(changetype<usize>(ref), changetype<usize>(oldParentRef), changetype<usize>(newParentRef));
} else {
// @ts-ignore: stub
__gc_retain(changetype<usize>(ref), changetype<usize>(newParentRef));
// @ts-ignore: stub
__gc_release(changetype<usize>(ref), changetype<usize>(oldParentRef));
}
}
}
/**
* Discards a runtime object that has not been registed and turned out to be unnecessary.
* Essentially undoes the forgoing ALLOCATE. Should be avoided where possible.
@ -274,12 +177,18 @@ function doMove(ref: usize, oldParentRef: usize, newParentRef: usize): void {
// @ts-ignore: decorator
@unsafe @inline
export function DISCARD(ref: usize): void {
doDiscard(ref);
discard(ref);
}
function doDiscard(ref: usize): void {
if (!ASC_NO_ASSERT) assertUnregistered(ref);
memory.free(changetype<usize>(ref - HEADER_SIZE));
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));
}
}
/**
@ -289,14 +198,14 @@ function doDiscard(ref: usize): void {
*/
// @ts-ignore: decorator
@unsafe @inline
export function MAKEARRAY<T>(capacity: i32, source: usize = 0): Array<T> {
return changetype<Array<T>>(doMakeArray(capacity, source, CLASSID<T[]>(), alignof<T>()));
export function MAKEARRAY<V>(capacity: i32, source: usize = 0): Array<V> {
return changetype<Array<V>>(makeArray(capacity, source, CLASSID<V[]>(), alignof<V>()));
}
function doMakeArray(capacity: i32, source: usize, classId: u32, alignLog2: usize): usize {
var array = doRegister(doAllocate(offsetof<i32[]>()), classId);
function makeArray(capacity: i32, source: usize, classId: u32, alignLog2: usize): usize {
var array = register(allocate(offsetof<i32[]>()), classId);
var bufferSize = <usize>capacity << alignLog2;
var buffer = doRegister(doAllocate(<usize>capacity << alignLog2), CLASSID<ArrayBuffer>());
var buffer = register(allocate(<usize>capacity << alignLog2), CLASSID<ArrayBuffer>());
changetype<ArrayBufferView>(array).data = changetype<ArrayBuffer>(buffer); // links
changetype<ArrayBufferView>(array).dataStart = buffer;
changetype<ArrayBufferView>(array).dataLength = bufferSize;
@ -305,22 +214,6 @@ function doMakeArray(capacity: i32, source: usize, classId: u32, alignLog2: usiz
return array;
}
// Helpers
/** Asserts that a managed object is still unregistered. */
// @ts-ignore: decorator
function assertUnregistered(ref: usize): void {
assert(ref > HEAP_BASE); // must be a heap object
assert(changetype<HEADER>(ref - HEADER_SIZE).classId == HEADER_MAGIC);
}
/** Asserts that a managed object has already been registered. */
// @ts-ignore: decorator
function assertRegistered(ref: usize): void {
assert(ref !== null); // may be a static string or buffer (not a heap object)
assert(changetype<HEADER>(ref - HEADER_SIZE).classId != HEADER_MAGIC);
}
import { ArrayBuffer } from "./arraybuffer";
import { E_INVALIDLENGTH } from "./util/error";
@ -332,14 +225,17 @@ export const MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE;
/** Hard wired ArrayBufferView interface. */
export abstract class ArrayBufferView {
/** Backing buffer. */
// @ts-ignore: decorator
@unsafe
data: ArrayBuffer;
/** Data start offset in memory. */
// @ts-ignore: decorator
@unsafe
dataStart: usize;
/** Data length in memory, counted from `dataStart`. */
// @ts-ignore: decorator
@unsafe
dataLength: u32;

View File

@ -1,4 +1,5 @@
import { RETAIN, RELEASE } from "./runtime";
/// <reference path="./collector/index.d.ts" />
import { HASH } from "./util/hash";
// A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
@ -105,11 +106,22 @@ export class Set<K> {
}
// append new entry
let entries = this.entries;
entry = changetype<SetEntry<K>>(
changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K>()
);
entry = changetype<SetEntry<K>>(changetype<usize>(entries) + this.entriesOffset++ * ENTRY_SIZE<K>());
entry.key = key;
// link with the set
entry.key = isManaged<K>() ? RETAIN<K,this>(key, this) : key;
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);
}
}
++this.entriesCount;
// link with previous entry in bucket
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
@ -121,7 +133,20 @@ export class Set<K> {
delete(key: K): bool {
var entry = this.find(key, HASH<K>(key));
if (!entry) return false;
if (isManaged<K>()) RELEASE<K,this>(entry.key, this);
if (isManaged<K>()) {
key = entry.key; // exact, e.g. string
if (isNullable<K>()) {
if (key !== null) {
if (isDefined(__ref_link)) __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)) __ref_unlink(changetype<usize>(key), changetype<usize>(this));
else if (isDefined(__ref_retain)) __ref_release(changetype<usize>(key));
else assert(false);
}
}
entry.taggedNext |= EMPTY;
--this.entriesCount;
// check if rehashing is appropriate

View File

@ -1,4 +1,6 @@
import { ALLOCATE, REGISTER, HEADER, HEADER_SIZE, RETAIN, MAKEARRAY, ArrayBufferView } from "./runtime";
/// <reference path="./collector/index.d.ts" />
import { ALLOCATE, REGISTER, HEADER, HEADER_SIZE, MAKEARRAY, ArrayBufferView } from "./runtime";
import { MAX_SIZE_32 } from "./util/allocator";
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
import { E_INVALIDLENGTH } from "./util/error";
@ -82,7 +84,7 @@ import { E_INVALIDLENGTH } from "./util/error";
return !compareImpl(this, start, searchString, 0, searchLength);
}
@operator("==") private static __eq(left: String, right: String): bool {
@operator("==") private static __eq(left: String | null, right: String | null): bool {
if (left === right) return true;
if (left === null || right === null) return false;
var leftLength = left.length;
@ -91,11 +93,11 @@ import { E_INVALIDLENGTH } from "./util/error";
return !compareImpl(left, 0, right, 0, leftLength);
}
@operator("!=") private static __ne(left: String, right: String): bool {
@operator("!=") private static __ne(left: String | null, right: String | null): bool {
return !this.__eq(left, right);
}
@operator(">") private static __gt(left: String, right: String): bool {
@operator(">") private static __gt(left: String | null, right: String | null): bool {
if (left === right || left === null || right === null) return false;
var leftLength = left.length;
var rightLength = right.length;
@ -359,16 +361,13 @@ import { E_INVALIDLENGTH } from "./util/error";
let resultStart = changetype<ArrayBufferView>(result).dataStart;
for (let i: isize = 0; i < length; ++i) {
let charStr = REGISTER<String>(ALLOCATE(2));
store<u16>(
changetype<usize>(charStr),
load<u16>(changetype<usize>(this) + (<usize>i << 1))
);
// result[i] = charStr
store<String>(resultStart + (<usize>i << alignof<usize>()),
isManaged<String>()
? RETAIN<String,Array<String>>(charStr, result)
: charStr
);
store<u16>(changetype<usize>(charStr), load<u16>(changetype<usize>(this) + (<usize>i << 1)));
store<String>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
if (isManaged<String>()) {
if (isDefined(__ref_link)) __ref_link(changetype<usize>(charStr), changetype<usize>(result));
else if (isDefined(__ref_retain)) __ref_retain(changetype<usize>(charStr));
else assert(false);
}
}
return result;
} else if (!length) {