mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-18 01:11:32 +00:00
use gc interface directly, document
This commit is contained in:
@ -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(
|
||||
|
107
std/assembly/collector/README.md
Normal file
107
std/assembly/collector/README.md
Normal 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.
|
@ -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
11
std/assembly/collector/index.d.ts
vendored
Normal 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;
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user