decisions

This commit is contained in:
dcode
2019-03-15 09:26:31 +01:00
parent 139cec0846
commit 968b0321a0
20 changed files with 619 additions and 557 deletions

View File

@ -1,5 +1,4 @@
import { runtime, ArrayBufferView } from "./runtime";
import { gc } from "./gc";
import { ALLOCATE, REALLOCATE, DISCARD, LINK, REGISTER, MAX_BYTELENGTH, 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";
@ -8,7 +7,7 @@ import { isArray as builtin_isArray } from "./builtins";
export class Array<T> extends ArrayBufferView {
private length_: i32;
@inline static isArray<U>(value: U): bool {
static isArray<U>(value: U): bool {
return builtin_isArray(value) && value !== null;
}
@ -34,10 +33,10 @@ export class Array<T> extends ArrayBufferView {
var oldData = this.data;
var oldCapacity = oldData.byteLength >>> alignof<T>();
if (<u32>length > <u32>oldCapacity) {
const MAX_LENGTH = ArrayBufferView.MAX_BYTELENGTH >>> alignof<T>();
const MAX_LENGTH = MAX_BYTELENGTH >>> alignof<T>();
if (<u32>length > <u32>MAX_LENGTH) throw new RangeError("Invalid array length");
let newCapacity = <usize>length << alignof<T>();
let newData = runtime.realloc(changetype<usize>(oldData), newCapacity); // registers on move
let newData = REALLOCATE(changetype<usize>(oldData), newCapacity); // registers on move
if (newData !== changetype<usize>(oldData)) {
this.data = changetype<ArrayBuffer>(newData); // links
this.dataStart = newData;
@ -77,7 +76,7 @@ export class Array<T> extends ArrayBufferView {
private __set(index: i32, value: T): void {
this.resize(index + 1);
store<T>(this.dataStart + (<usize>index << alignof<T>()), value);
if (isManaged<T>()) gc.link(value, this);
if (isManaged<T>()) LINK(value, this);
if (index >= this.length_) this.length_ = index + 1;
}
@ -142,7 +141,7 @@ export class Array<T> extends ArrayBufferView {
this.resize(newLength);
this.length_ = newLength;
store<T>(this.dataStart + (<usize>(newLength - 1) << alignof<T>()), element);
if (isManaged<T>()) gc.link(element, this);
if (isManaged<T>()) LINK(element, this);
return newLength;
}
@ -157,14 +156,14 @@ export class Array<T> extends ArrayBufferView {
for (let offset: usize = 0; offset < thisSize; offset += sizeof<T>()) {
let element = load<T>(thisStart + offset);
store<T>(outStart + offset, element);
gc.link(element, out);
LINK(element, out);
}
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, element);
gc.link(element, out);
LINK(element, out);
}
} else {
memory.copy(outStart, this.dataStart, thisSize);
@ -222,7 +221,7 @@ export class Array<T> extends ArrayBufferView {
let value = load<T>(this.dataStart + (<usize>index << alignof<T>()));
let result = callbackfn(value, index, this);
store<U>(outStart + (<usize>index << alignof<U>()), result);
if (isManaged<U>()) gc.link(result, out);
if (isManaged<U>()) LINK(result, out);
}
return out;
}
@ -294,7 +293,7 @@ export class Array<T> extends ArrayBufferView {
<usize>(newLength - 1) << alignof<T>()
);
store<T>(base, element);
if (isManaged<T>()) gc.link(element, this);
if (isManaged<T>()) LINK(element, this);
this.length_ = newLength;
return newLength;
}
@ -311,7 +310,7 @@ export class Array<T> extends ArrayBufferView {
let offset = <usize>i << alignof<T>();
let element = load<T>(thisBase + offset);
store<T>(sliceBase + offset, element);
if (isManaged<T>()) gc.link(element, slice);
if (isManaged<T>()) LINK(element, slice);
}
return slice;
}
@ -327,7 +326,7 @@ export class Array<T> extends ArrayBufferView {
for (let i = 0; i < deleteCount; ++i) {
let element = load<T>(thisBase + (<usize>i << alignof<T>()));
store<T>(spliceStart + (<usize>i << alignof<T>()), element);
if (isManaged<T>()) gc.link(element, splice);
if (isManaged<T>()) LINK(element, splice);
}
memory.copy(
splice.dataStart,
@ -397,7 +396,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 = runtime.alloc(estLen << 1);
var result = ALLOCATE(estLen << 1);
var offset = 0;
var value: bool;
for (let i = 0; i < lastIndex; ++i) {
@ -429,10 +428,10 @@ export class Array<T> extends ArrayBufferView {
if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset);
runtime.freeUnregistered(result);
DISCARD(result);
return trimmed; // registered in .substring
}
return gc.register<string>(result);
return REGISTER<string>(result);
}
private join_int(separator: string = ","): string {
@ -445,7 +444,7 @@ export class Array<T> extends ArrayBufferView {
var sepLen = separator.length;
const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + i32(isSigned<T>());
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = runtime.alloc(estLen << 1);
var result = ALLOCATE(estLen << 1);
var offset = 0;
var value: T;
for (let i = 0; i < lastIndex; ++i) {
@ -466,10 +465,10 @@ export class Array<T> extends ArrayBufferView {
offset += itoa_stream<T>(result, offset, value);
if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset);
runtime.freeUnregistered(result);
DISCARD(result);
return trimmed; // registered in .substring
}
return gc.register<string>(result);
return REGISTER<string>(result);
}
private join_flt(separator: string = ","): string {
@ -486,7 +485,7 @@ export class Array<T> extends ArrayBufferView {
const valueLen = MAX_DOUBLE_LENGTH;
var sepLen = separator.length;
var estLen = (valueLen + sepLen) * lastIndex + valueLen;
var result = runtime.alloc(estLen << 1);
var result = ALLOCATE(estLen << 1);
var offset = 0;
var value: T;
for (let i = 0; i < lastIndex; ++i) {
@ -511,10 +510,10 @@ export class Array<T> extends ArrayBufferView {
);
if (estLen > offset) {
let trimmed = changetype<string>(result).substring(0, offset);
runtime.freeUnregistered(result);
DISCARD(result);
return trimmed; // registered in .substring
}
return gc.register<string>(result);
return REGISTER<string>(result);
}
private join_str(separator: string = ","): string {
@ -529,7 +528,7 @@ export class Array<T> extends ArrayBufferView {
estLen += load<string>(dataStart + (<usize>i << alignof<T>())).length;
}
var offset = 0;
var result = runtime.alloc((estLen + sepLen * lastIndex) << 1);
var result = ALLOCATE((estLen + sepLen * lastIndex) << 1);
var value: String;
for (let i = 0; i < lastIndex; ++i) {
value = load<string>(dataStart + (<usize>i << alignof<T>()));
@ -560,7 +559,7 @@ export class Array<T> extends ArrayBufferView {
<usize>valueLen << 1
);
}
return gc.register<string>(result);
return REGISTER<string>(result);
}
private join_arr(separator: string = ","): string {
@ -597,7 +596,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 = runtime.alloc(estLen << 1);
var result = ALLOCATE(estLen << 1);
var offset = 0;
var value: T;
for (let i = 0; i < lastIndex; ++i) {
@ -629,13 +628,12 @@ export class Array<T> extends ArrayBufferView {
}
if (estLen > offset) {
let out = changetype<string>(result).substring(0, offset);
runtime.freeUnregistered(result);
DISCARD(result);
return out; // registered in .substring
}
return gc.register<string>(result);
return REGISTER<string>(result);
}
@inline
toString(): string {
return this.join();
}

View File

@ -1,5 +1,4 @@
import { runtime, ArrayBufferView } from "./runtime";
import { gc } from "./gc";
import { ALLOCATE, REGISTER, HEADER, HEADER_SIZE, MAX_BYTELENGTH } from "./runtime";
@sealed export class ArrayBuffer {
@ -22,24 +21,24 @@ import { gc } from "./gc";
}
constructor(length: i32) {
if (<u32>length > <u32>ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length");
var buffer = runtime.alloc(<usize>length);
if (<u32>length > <u32>MAX_BYTELENGTH) throw new RangeError("Invalid array buffer length");
var buffer = ALLOCATE(<usize>length);
memory.fill(changetype<usize>(buffer), 0, <usize>length);
return gc.register<ArrayBuffer>(buffer);
return REGISTER<ArrayBuffer>(buffer);
}
get byteLength(): i32 {
return changetype<runtime.Header>(changetype<usize>(this) - runtime.Header.SIZE).payloadSize;
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize;
}
slice(begin: i32 = 0, end: i32 = ArrayBufferView.MAX_BYTELENGTH): ArrayBuffer {
slice(begin: i32 = 0, end: i32 = MAX_BYTELENGTH): 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 = runtime.alloc(outSize);
var out = ALLOCATE(outSize);
memory.copy(out, changetype<usize>(this) + <usize>begin, outSize);
return gc.register<ArrayBuffer>(out);
return REGISTER<ArrayBuffer>(out);
}
toString(): string {

View File

@ -9,8 +9,8 @@ const TRACE = false;
@inline
export const HEADER_SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
import { ITERATEROOTS } from "../runtime";
import { AL_MASK, MAX_SIZE_32 } from "../util/allocator";
import { gc } from "../gc";
/** Collector states. */
const enum State {
@ -140,7 +140,7 @@ function step(): void {
}
case State.IDLE: {
if (TRACE) trace("gc~step/IDLE");
gc.iterateRoots(__gc_mark);
ITERATEROOTS(__gc_mark);
state = State.MARK;
if (TRACE) trace("gc~state = MARK");
break;
@ -161,7 +161,7 @@ function step(): void {
obj.hookFn(objToRef(obj));
} else {
if (TRACE) trace("gc~step/MARK finish");
gc.iterateRoots(__gc_mark);
ITERATEROOTS(__gc_mark);
obj = iter.next;
if (obj === toSpace) {
let from = fromSpace;

View File

@ -1,5 +1,5 @@
import { MAX_BYTELENGTH } from "./runtime";
import { ArrayBuffer } from "./arraybuffer";
import { ArrayBufferView } from "./runtime";
export class DataView {
@ -13,7 +13,7 @@ export class DataView {
byteLength: i32 = i32.MIN_VALUE // FIXME
) {
if (byteLength === i32.MIN_VALUE) byteLength = buffer.byteLength - byteOffset; // FIXME
if (<u32>byteLength > <u32>ArrayBufferView.MAX_BYTELENGTH) throw new RangeError("Invalid byteLength");
if (<u32>byteLength > <u32>MAX_BYTELENGTH) throw new RangeError("Invalid byteLength");
if (<u32>byteOffset + byteLength > <u32>buffer.byteLength) throw new RangeError("Invalid length");
this.data = buffer; // links
var dataStart = changetype<usize>(buffer) + <usize>byteOffset;

View File

@ -1,45 +1,30 @@
import { runtime } from "./runtime";
/** Garbage collector interface. */
export namespace gc {
/** Whether the garbage collector interface is implemented. */
// @ts-ignore: decorator
@lazy
export const implemented: bool = isDefined(
export const IMPLEMENTED: bool = isDefined(
// @ts-ignore: stub
__gc_register
);
/** Gets the computed unique class id of a class type. */
// @ts-ignore: decorator
@unsafe @builtin
export declare function classId<T>(): u32;
/** Iterates reference root objects. */
// @ts-ignore: decorator
@unsafe @builtin
export declare function iterateRoots(fn: (ref: usize) => void): void;
/** Registers a managed object to be tracked by the garbage collector. */
// @ts-ignore: decorator
@unsafe @inline
export function register<T>(ref: usize): T {
runtime.unrefUnregistered(ref).classId = classId<T>();
export function register(ref: usize): void {
// @ts-ignore: stub
if (isDefined(__gc_register)) __gc_register(ref);
return changetype<T>(ref);
else ERROR("missing implementation: gc.register");
}
/** Links a registered object with the registered object now referencing it. */
// @ts-ignore: decorator
@unsafe @inline
export function link<T, TParent>(ref: T, parentRef: TParent): void {
assert(changetype<usize>(ref) >= HEAP_BASE + runtime.Header.SIZE); // must be a heap object
var header = changetype<runtime.Header>(changetype<usize>(ref) - runtime.Header.SIZE);
assert(header.classId != runtime.Header.MAGIC && header.gc1 != 0 && header.gc2 != 0); // must be registered
export function link(ref: usize, parentRef: usize): void {
// @ts-ignore: stub
if (isDefined(__gc_link)) __gc_link(ref, parentRef);
else ERROR("missing implementation: gc.link");
}
/** Marks an object as being reachable. */

View File

@ -1515,36 +1515,16 @@ declare function unmanaged(constructor: Function): void;
declare function sealed(constructor: Function): void;
/** Annotates a method, function or constant global as always inlined. */
declare function inline(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void;
declare function inline(...args: any[]): any;
/** Annotates a method, function or constant global as unsafe. */
declare function unsafe(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void;
declare function unsafe(...args: any[]): any;
/** Annotates an explicit external name of a function or global. */
declare function external(namespace: string, name: string): (
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>
) => TypedPropertyDescriptor<any> | void;
declare function external(...args: any[]): any;
/** Annotates a global for lazy compilation. */
declare function lazy(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void;
declare function lazy(...args: any[]): any;
/** Annotates a function as the explicit start function. */
declare function start(
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void;
declare function start(...args: any[]): any;

View File

@ -1,4 +1,4 @@
import { gc } from "./gc";
import { LINK } from "./runtime";
import { HASH } from "./util/hash";
// A deterministic hash map based on CloseTable from https://github.com/jorendorff/dht
@ -8,12 +8,12 @@ import { HASH } from "./util/hash";
const INITIAL_CAPACITY = 4;
// @ts-ignore: decorator
@inline const
FILL_FACTOR: f64 = 8 / 3;
@inline
const FILL_FACTOR: f64 = 8 / 3;
// @ts-ignore: decorator
@inline const
FREE_FACTOR: f64 = 3 / 4;
@inline
const FREE_FACTOR: f64 = 3 / 4;
/** Structure of a map entry. */
@unmanaged class MapEntry<K,V> {
@ -124,8 +124,8 @@ export class Map<K,V> {
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
entry.taggedNext = load<usize>(bucketPtrBase);
store<usize>(bucketPtrBase, changetype<usize>(entry));
if (isManaged<K>()) gc.link(key, this);
if (isManaged<V>()) gc.link(value, this);
if (isManaged<K>()) LINK(key, this);
if (isManaged<V>()) LINK(value, this);
}
}

View File

@ -1,135 +1,166 @@
import { AL_MASK, MAX_SIZE_32 } from "./util/allocator";
import { HEAP_BASE, memory } from "./memory";
import { gc } from "./gc";
/** Common runtime. */
export namespace runtime {
/** Whether the memory manager interface is implemented. */
// @ts-ignore: decorator, stub
@lazy export const MM_IMPLEMENTED: bool = isDefined(__memory_allocate);
/** Common runtime header of all objects. */
@unmanaged export class Header {
/** Whether the garbage collector interface is implemented. */
// @ts-ignore: decorator, stub
@lazy export const GC_IMPLEMENTED: bool = isDefined(__gc_register);
/** Size of a runtime header. */
// @ts-ignore: decorator
@lazy @inline
static readonly SIZE: usize = gc.implemented
? (offsetof<runtime.Header>( ) + AL_MASK) & ~AL_MASK // full header if GC is present
: (offsetof<runtime.Header>("gc1") + AL_MASK) & ~AL_MASK; // half header if GC is absent
/** Common runtime header. Each managed object has one. */
@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. Only present if GC is. */
gc1: usize; // itcm: tagged next
/** Reserved field for use by GC. Only present if GC is. */
gc2: usize; // itcm: prev
}
/** Magic value used to validate runtime headers. */
// @ts-ignore: decorator
@lazy @inline
static readonly MAGIC: u32 = 0xA55E4B17;
/** 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
/** 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. Only present if GC is. */
gc1: usize; // itcm: tagged next
/** Reserved field for use by GC. Only present if GC is. */
gc2: usize; // itcm: prev
/** Common runtime header magic. Used to assert registered/unregistered status. */
export const HEADER_MAGIC: u32 = 0xA55E4B17;
/** Gets the computed unique class id of a class type. */
// @ts-ignore: decorator
@unsafe @builtin
export declare function CLASSID<T>(): u32;
/** Iterates over all root objects of a reference type. */
// @ts-ignore: decorator
@unsafe @builtin
export declare function ITERATEROOTS(fn: (ref: usize) => void): void;
/** 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 a new object and returns a pointer to its payload. Does not fill. */
// @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 (GC_IMPLEMENTED) {
header.gc1 = 0;
header.gc2 = 0;
}
return changetype<usize>(header) + HEADER_SIZE;
}
// Note that header data and layout isn't quite optimal depending on which allocator one
// decides to use, but it's done this way for maximum flexibility. Also remember that the
// runtime will most likely change significantly once reftypes and WASM GC are a thing.
/** Adjusts an allocation to actual block size. Primarily targets TLSF. */
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 a new object and returns a pointer to its payload. Does not fill. */
// @ts-ignore: decorator
@unsafe
export function alloc(payloadSize: u32): usize {
var header = changetype<Header>(memory.allocate(adjust(payloadSize)));
header.classId = Header.MAGIC;
header.payloadSize = payloadSize;
if (gc.implemented) {
header.gc1 = 0;
header.gc2 = 0;
}
return changetype<usize>(header) + Header.SIZE;
}
/** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */
// @ts-ignore: decorator
@unsafe
export function realloc(ref: usize, newPayloadSize: u32): 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 (gc.implemented) {
newHeader.gc1 = 0;
newHeader.gc2 = 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 (gc.implemented) {
// if previously registered, register again
// @ts-ignore: stub
__gc_register(ref);
}
header = newHeader;
ref = newRef;
} else {
// otherwise just clear additional memory within this block
memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize);
/** Reallocates an object if necessary. Returns a pointer to its (moved) payload. */
// @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 (GC_IMPLEMENTED) {
newHeader.gc1 = 0;
newHeader.gc2 = 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 (GC_IMPLEMENTED) {
// if previously registered, register again
// @ts-ignore: stub
__gc_register(ref);
}
header = newHeader;
ref = newRef;
} 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.
// otherwise just clear additional memory within this block
memory.fill(ref + payloadSize, 0, newPayloadSize - payloadSize);
}
header.payloadSize = newPayloadSize;
return ref;
} 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;
}
// @ts-ignore: decorator
@unsafe
export function unrefUnregistered(ref: usize): Header {
assert(ref >= HEAP_BASE + Header.SIZE); // must be a heap object
var header = changetype<Header>(ref - Header.SIZE);
assert(header.classId == Header.MAGIC); // must be unregistered
return header;
}
/** Registers a managed object to be tracked by the garbage collector, if present. */
// @ts-ignore: decorator
@unsafe @inline
export function REGISTER<T>(ref: usize): T {
ASSERT_UNREGISTERED(ref);
changetype<HEADER>(ref - HEADER_SIZE).classId = CLASSID<T>();
// @ts-ignore: stub
if (GC_IMPLEMENTED) __gc_register(ref);
return changetype<T>(ref);
}
/** Frees an unregistered object that turned out to be unnecessary. */
// @ts-ignore: decorator
@unsafe @inline
export function freeUnregistered<T>(ref: T): void {
memory.free(changetype<usize>(unrefUnregistered(changetype<usize>(ref))));
}
/** Links a registered object with the (registered) object now referencing it. */
// @ts-ignore: decorator
@unsafe @inline
export function LINK<T,TParent>(ref: T, parentRef: TParent): void {
ASSERT_REGISTERED(changetype<usize>(ref));
ASSERT_REGISTERED(changetype<usize>(parentRef));
// @ts-ignore: stub
if (GC_IMPLEMENTED) __gc_link(changetype<usize>(ref), changetype<usize>(parentRef));
}
/** Discards an unregistered object that turned out to be unnecessary. */
// @ts-ignore: decorator
export function DISCARD(ref: usize): void {
ASSERT_UNREGISTERED(ref);
memory.free(changetype<usize>(ref - HEADER_SIZE));
}
// Helpers
/** Asserts that a managed object is still unregistered. */
function ASSERT_UNREGISTERED(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. */
function ASSERT_REGISTERED(ref: usize): void {
assert(ref > HEAP_BASE); // must be a heap object
assert(changetype<HEADER>(ref - HEADER_SIZE).classId != HEADER_MAGIC);
}
import { ArrayBuffer } from "./arraybuffer";
/** Maximum byte length of any buffer. */
// @ts-ignore: decorator
@lazy
export const MAX_BYTELENGTH: i32 = MAX_SIZE_32 - HEADER_SIZE;
/** Hard wired ArrayBufferView interface. */
export abstract class ArrayBufferView {
// @ts-ignore: decorator
@lazy
static readonly MAX_BYTELENGTH: i32 = MAX_SIZE_32 - runtime.Header.SIZE;
[key: number]: number;
// @ts-ignore: decorator
@ -145,7 +176,7 @@ export abstract class ArrayBufferView {
dataEnd: usize;
constructor(length: i32, alignLog2: i32) {
if (<u32>length > <u32>ArrayBufferView.MAX_BYTELENGTH >>> alignLog2) throw new RangeError("Invalid length");
if (<u32>length > <u32>MAX_BYTELENGTH >>> alignLog2) throw new RangeError("Invalid length");
var buffer = new ArrayBuffer(length << alignLog2);
this.data = buffer;
this.dataStart = changetype<usize>(buffer);
@ -161,7 +192,7 @@ export abstract class ArrayBufferView {
}
get length(): i32 {
ERROR("missing implementation: [T extends ArrayBufferView]#length");
ERROR("missing implementation: subclasses must implement ArrayBufferView#length");
return unreachable();
}
}

View File

@ -1,4 +1,4 @@
import { gc } from "./gc";
import { LINK } from "./runtime";
import { HASH } from "./util/hash";
// A deterministic hash set based on CloseTable from https://github.com/jorendorff/dht
@ -114,7 +114,7 @@ export class Set<K> {
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
entry.taggedNext = load<usize>(bucketPtrBase);
store<usize>(bucketPtrBase, changetype<usize>(entry));
if (isManaged<K>()) gc.link(key, this);
if (isManaged<K>()) LINK(key, this);
}
}

View File

@ -1,29 +1,26 @@
import { runtime } from "./runtime";
import { gc } from "./gc";
import { ALLOCATE, REGISTER, HEADER, HEADER_SIZE } from "./runtime";
import { MAX_SIZE_32 } from "./util/allocator";
import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./util/string";
@sealed export abstract class String {
// @ts-ignore: decorator
@lazy
static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - runtime.Header.SIZE) >> alignof<u16>();
@lazy static readonly MAX_LENGTH: i32 = (MAX_SIZE_32 - HEADER_SIZE) >> alignof<u16>();
get length(): i32 {
return changetype<runtime.Header>(changetype<usize>(this) - runtime.Header.SIZE).payloadSize >> 1;
return changetype<HEADER>(changetype<usize>(this) - HEADER_SIZE).payloadSize >> 1;
}
// TODO Add and handle second argument
static fromCharCode(code: i32): String {
var out = runtime.alloc(2);
var out = ALLOCATE(2);
store<u16>(out, <u16>code);
return gc.register<String>(out);
return REGISTER<String>(out);
}
static fromCodePoint(code: i32): String {
assert(<u32>code <= 0x10FFFF);
var sur = code > 0xFFFF;
var out = runtime.alloc((i32(sur) + 1) << 1);
var out = ALLOCATE((i32(sur) + 1) << 1);
if (!sur) {
store<u16>(out, <u16>code);
} else {
@ -32,15 +29,15 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
let lo: u32 = (code & 0x3FF) + 0xDC00;
store<u32>(out, (hi << 16) | lo);
}
return gc.register<String>(out);
return REGISTER<String>(out);
}
@operator("[]") charAt(pos: i32): String {
assert(this !== null);
if (<u32>pos >= <u32>this.length) return changetype<String>("");
var out = runtime.alloc(2);
var out = ALLOCATE(2);
store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1)));
return gc.register<String>(out);
return REGISTER<String>(out);
}
charCodeAt(pos: i32): i32 {
@ -71,10 +68,10 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
var otherSize: isize = other.length << 1;
var outSize: usize = thisSize + otherSize;
if (outSize == 0) return changetype<String>("");
var out = runtime.alloc(outSize);
var out = ALLOCATE(outSize);
memory.copy(out, changetype<usize>(this), thisSize);
memory.copy(out + thisSize, changetype<usize>(other), otherSize);
return gc.register<String>(out);
return REGISTER<String>(out);
}
endsWith(searchString: String, endPosition: i32 = String.MAX_LENGTH): bool {
@ -184,9 +181,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
if (intStart < 0) intStart = max(size + intStart, 0);
var resultLength = min(max(end, 0), size - intStart);
if (resultLength <= 0) return changetype<String>("");
var out = runtime.alloc(resultLength << 1);
var out = ALLOCATE(resultLength << 1);
memory.copy(out, changetype<usize>(this) + intStart, resultLength);
return gc.register<String>(out);
return REGISTER<String>(out);
}
substring(start: i32, end: i32 = i32.MAX_VALUE): String {
@ -199,9 +196,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
len = toPos - fromPos;
if (!len) return changetype<String>("");
if (!fromPos && toPos == this.length << 1) return this;
var out = runtime.alloc(len);
var out = ALLOCATE(len);
memory.copy(out, changetype<usize>(this) + fromPos, len);
return gc.register<String>(out);
return REGISTER<String>(out);
}
trim(): String {
@ -227,9 +224,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
}
if (!size) return changetype<String>("");
if (!start && size == length << 1) return this;
var out = runtime.alloc(size);
var out = ALLOCATE(size);
memory.copy(out, changetype<usize>(this) + offset, size);
return gc.register<String>(out);
return REGISTER<String>(out);
}
@inline
@ -257,9 +254,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
if (!offset) return this;
size -= offset;
if (!size) return changetype<String>("");
var out = runtime.alloc(size);
var out = ALLOCATE(size);
memory.copy(out, changetype<usize>(this) + offset, size);
return gc.register<String>(out);
return REGISTER<String>(out);
}
trimEnd(): String {
@ -276,9 +273,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
}
if (!size) return changetype<String>("");
if (size == originalSize) return this;
var out = runtime.alloc(size);
var out = ALLOCATE(size);
memory.copy(out, changetype<usize>(this), size);
return gc.register<String>(out);
return REGISTER<String>(out);
}
padStart(targetLength: i32, padString: string = " "): String {
@ -288,7 +285,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this;
var prependSize = targetSize - thisSize;
var out = runtime.alloc(targetSize);
var out = ALLOCATE(targetSize);
if (prependSize > padSize) {
let repeatCount = (prependSize - 2) / padSize;
let restBase = repeatCount * padSize;
@ -299,7 +296,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
memory.copy(out, changetype<usize>(padString), prependSize);
}
memory.copy(out + prependSize, changetype<usize>(this), thisSize);
return gc.register<String>(out);
return REGISTER<String>(out);
}
padEnd(targetLength: i32, padString: string = " "): String {
@ -309,7 +306,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
var padSize = <usize>padString.length << 1;
if (targetSize < thisSize || !padSize) return this;
var appendSize = targetSize - thisSize;
var out = runtime.alloc(targetSize);
var out = ALLOCATE(targetSize);
memory.copy(out, changetype<usize>(this), thisSize);
if (appendSize > padSize) {
let repeatCount = (appendSize - 2) / padSize;
@ -320,7 +317,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
} else {
memory.copy(out + thisSize, changetype<usize>(padString), appendSize);
}
return gc.register<String>(out);
return REGISTER<String>(out);
}
repeat(count: i32 = 0): String {
@ -334,9 +331,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
if (count == 0 || !length) return changetype<String>("");
if (count == 1) return this;
var out = runtime.alloc((length * count) << 1);
var out = ALLOCATE((length * count) << 1);
memory.repeat(out, changetype<usize>(this), <usize>length << 1, count);
return gc.register<String>(out);
return REGISTER<String>(out);
}
slice(beginIndex: i32, endIndex: i32 = i32.MAX_VALUE): String {
@ -345,9 +342,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
var end = endIndex < 0 ? max(endIndex + len, 0) : min(endIndex, len);
len = end - begin;
if (len <= 0) return changetype<String>("");
var out = runtime.alloc(len << 1);
var out = ALLOCATE(len << 1);
memory.copy(out, changetype<usize>(this) + (<usize>begin << 1), <usize>len << 1);
return gc.register<String>(out);
return REGISTER<String>(out);
}
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
@ -365,7 +362,7 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
let buffer = unreachable(); // TODO
// let buffer = <ArrayBuffer>result.buffer_;
for (let i: isize = 0; i < length; ++i) {
let char = runtime.alloc(2);
let char = ALLOCATE(2);
store<u16>(
changetype<usize>(char),
load<u16>(
@ -385,9 +382,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
while ((end = this.indexOf(separator!, start)) != -1) {
let len = end - start;
if (len > 0) {
let out = runtime.alloc(<usize>len << 1);
let out = ALLOCATE(<usize>len << 1);
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(gc.register<String>(out));
result.push(REGISTER<String>(out));
} else {
result.push(changetype<String>(""));
}
@ -401,9 +398,9 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
}
var len = length - start;
if (len > 0) {
let out = runtime.alloc(<usize>len << 1);
let out = ALLOCATE(<usize>len << 1);
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(gc.register<String>(out));
result.push(REGISTER<String>(out));
} else {
result.push(changetype<String>(""));
}
@ -475,10 +472,10 @@ import { compareImpl, parse, CharCode, isWhiteSpaceOrLineTerminator } from "./ut
}
}
assert(ptrPos == len);
var out = runtime.alloc(bufPos);
var out = ALLOCATE(bufPos);
memory.copy(changetype<usize>(out), buf, bufPos);
memory.free(buf);
return gc.register<string>(out);
return REGISTER<string>(out);
}
toUTF8(): usize {

View File

@ -1,5 +1,4 @@
import { runtime, ArrayBufferView } from "./runtime";
import { gc } from "./gc";
import { ALLOCATE, REGISTER, LINK, ArrayBufferView } from "./runtime";
import { COMPARATOR, SORT as SORT_IMPL } from "./util/sort";
function clampToByte(value: i32): i32 {
@ -804,11 +803,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 = runtime.alloc(offsetof<TArray>());
var out = ALLOCATE(offsetof<TArray>());
store<usize>(out, buffer, offsetof<TArray>("buffer"));
store<usize>(out, array.dataStart + (<usize>begin << alignof<T>()) , offsetof<TArray>("dataStart"));
store<usize>(out, array.dataEnd + (<usize>(end - begin) << alignof<T>()), offsetof<TArray>("dataEnd"));
gc.link(buffer, gc.register<TArray>(out)); // register first, then link
LINK(buffer, REGISTER<TArray>(out)); // register first, then link
return changetype<TArray>(out);
}

View File

@ -1,5 +1,4 @@
import { runtime, ArrayBufferView } from "../runtime";
import { gc } from "../gc";
import { ALLOCATE, REGISTER, DISCARD, ArrayBufferView } from "../runtime";
import { CharCode } from "./string";
// @ts-ignore: decorator
@ -263,10 +262,10 @@ export function utoa32(value: u32): String {
if (!value) return "0";
var decimals = decimalCount32(value);
var out = runtime.alloc(decimals << 1);
var out = ALLOCATE(decimals << 1);
utoa32_core(changetype<usize>(out), value, decimals);
return gc.register<String>(out);
return REGISTER<String>(out);
}
export function itoa32(value: i32): String {
@ -276,12 +275,12 @@ export function itoa32(value: i32): String {
if (sign) value = -value;
var decimals = decimalCount32(value) + u32(sign);
var out = runtime.alloc(decimals << 1);
var out = ALLOCATE(decimals << 1);
utoa32_core(changetype<usize>(out), value, decimals);
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
return gc.register<String>(out);
return REGISTER<String>(out);
}
export function utoa64(value: u64): String {
@ -291,14 +290,14 @@ export function utoa64(value: u64): String {
if (value <= u32.MAX_VALUE) {
let val32 = <u32>value;
let decimals = decimalCount32(val32);
out = runtime.alloc(decimals << 1);
out = ALLOCATE(decimals << 1);
utoa32_core(out, val32, decimals);
} else {
let decimals = decimalCount64(value);
out = runtime.alloc(decimals << 1);
out = ALLOCATE(decimals << 1);
utoa64_core(changetype<usize>(out), value, decimals);
}
return gc.register<String>(out);
return REGISTER<String>(out);
}
export function itoa64(value: i64): String {
@ -311,16 +310,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 = runtime.alloc(decimals << 1);
out = ALLOCATE(decimals << 1);
utoa32_core(changetype<usize>(out), val32, decimals);
} else {
let decimals = decimalCount64(value) + u32(sign);
out = runtime.alloc(decimals << 1);
out = ALLOCATE(decimals << 1);
utoa64_core(changetype<usize>(out), value, decimals);
}
if (sign) store<u16>(changetype<usize>(out), CharCode.MINUS);
return gc.register<String>(out);
return REGISTER<String>(out);
}
export function itoa<T extends number>(value: T): String {
@ -625,10 +624,10 @@ export function dtoa(value: f64): String {
if (isNaN(value)) return "NaN";
return select<String>("-Infinity", "Infinity", value < 0);
}
var temp = runtime.alloc(MAX_DOUBLE_LENGTH << 1);
var temp = ALLOCATE(MAX_DOUBLE_LENGTH << 1);
var length = dtoa_core(temp, value);
var result = changetype<String>(temp).substring(0, length);
runtime.freeUnregistered(temp);
DISCARD(temp);
return result;
}