Initial GC integration (#196)

This commit is contained in:
Daniel Wirtz
2018-08-02 18:23:02 +02:00
committed by GitHub
parent 671121bf70
commit dc0f271fc2
139 changed files with 7370 additions and 5016 deletions

View File

@ -1,8 +1,8 @@
import {
MAX_BLENGTH,
HEADER_SIZE as HEADER_SIZE_AB,
allocUnsafe,
reallocUnsafe,
HEADER_SIZE,
allocateUnsafe,
reallocateUnsafe,
loadUnsafe,
storeUnsafe
} from "./internal/arraybuffer";
@ -22,11 +22,11 @@ export class Array<T> {
const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>();
if (<u32>length > <u32>MAX_LENGTH) throw new RangeError("Invalid array length");
var byteLength = length << alignof<T>();
var buffer = allocUnsafe(byteLength);
var buffer = allocateUnsafe(byteLength);
this.buffer_ = buffer;
this.length_ = length;
memory.fill(
changetype<usize>(buffer) + HEADER_SIZE_AB,
changetype<usize>(buffer) + HEADER_SIZE,
0,
<usize>byteLength
);
@ -42,7 +42,7 @@ export class Array<T> {
if (<u32>length > <u32>capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>();
if (<u32>length > <u32>MAX_LENGTH) throw new RangeError("Invalid array length");
buffer = reallocUnsafe(buffer, length << alignof<T>());
buffer = reallocateUnsafe(buffer, length << alignof<T>());
this.buffer_ = buffer;
}
this.length_ = length;
@ -84,16 +84,18 @@ export class Array<T> {
if (<u32>index >= <u32>capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>();
if (<u32>index >= <u32>MAX_LENGTH) throw new Error("Invalid array length");
buffer = reallocUnsafe(buffer, (index + 1) << alignof<T>());
buffer = reallocateUnsafe(buffer, (index + 1) << alignof<T>());
this.buffer_ = buffer;
this.length_ = index + 1;
}
storeUnsafe<T,T>(buffer, index, value);
if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(value)); // tslint:disable-line
}
@operator("{}=")
private __unchecked_set(index: i32, value: T): void {
storeUnsafe<T,T>(this.buffer_, index, value);
if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(value)); // tslint:disable-line
}
includes(searchElement: T, fromIndex: i32 = 0): bool {
@ -141,11 +143,12 @@ export class Array<T> {
if (<u32>length >= <u32>capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>();
if (<u32>length >= <u32>MAX_LENGTH) throw new Error("Invalid array length");
buffer = reallocUnsafe(buffer, newLength << alignof<T>());
buffer = reallocateUnsafe(buffer, newLength << alignof<T>());
this.buffer_ = buffer;
}
this.length_ = newLength;
storeUnsafe<T,T>(buffer, length, element);
if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(element)); // tslint:disable-line
return newLength;
}
@ -217,8 +220,8 @@ export class Array<T> {
var element = loadUnsafe<T,T>(buffer, 0);
var lastIndex = length - 1;
memory.copy(
changetype<usize>(buffer) + HEADER_SIZE_AB,
changetype<usize>(buffer) + HEADER_SIZE_AB + sizeof<T>(),
changetype<usize>(buffer) + HEADER_SIZE,
changetype<usize>(buffer) + HEADER_SIZE + sizeof<T>(),
<usize>lastIndex << alignof<T>()
);
storeUnsafe<T,T>(buffer, lastIndex, <T>null);
@ -242,17 +245,18 @@ export class Array<T> {
if (<u32>length >= <u32>capacity) {
const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>();
if (<u32>length >= <u32>MAX_LENGTH) throw new Error("Invalid array length");
buffer = reallocUnsafe(buffer, newLength << alignof<T>());
buffer = reallocateUnsafe(buffer, newLength << alignof<T>());
capacity = buffer.byteLength >>> alignof<T>();
this.buffer_ = buffer;
}
memory.copy(
changetype<usize>(buffer) + HEADER_SIZE_AB + sizeof<T>(),
changetype<usize>(buffer) + HEADER_SIZE_AB,
changetype<usize>(buffer) + HEADER_SIZE + sizeof<T>(),
changetype<usize>(buffer) + HEADER_SIZE,
<usize>(capacity - 1) << alignof<T>()
);
storeUnsafe<T,T>(buffer, 0, element);
this.length_ = newLength;
if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(element)); // tslint:disable-line
return newLength;
}
@ -268,8 +272,8 @@ export class Array<T> {
var sliced = new Array<T>(newLength);
if (newLength) {
memory.copy(
changetype<usize>(sliced.buffer_) + HEADER_SIZE_AB,
changetype<usize>(this.buffer_) + HEADER_SIZE_AB + (<usize>begin << alignof<T>()),
changetype<usize>(sliced.buffer_) + HEADER_SIZE,
changetype<usize>(this.buffer_) + HEADER_SIZE + (<usize>begin << alignof<T>()),
<usize>newLength << alignof<T>()
);
}
@ -284,8 +288,8 @@ export class Array<T> {
deleteCount = min(deleteCount, length - start);
var buffer = this.buffer_;
memory.copy(
changetype<usize>(buffer) + HEADER_SIZE_AB + (<usize>start << alignof<T>()),
changetype<usize>(buffer) + HEADER_SIZE_AB + (<usize>(start + deleteCount) << alignof<T>()),
changetype<usize>(buffer) + HEADER_SIZE + (<usize>start << alignof<T>()),
changetype<usize>(buffer) + HEADER_SIZE + (<usize>(start + deleteCount) << alignof<T>()),
<usize>deleteCount << alignof<T>()
);
this.length_ = length - deleteCount;
@ -328,4 +332,16 @@ export class Array<T> {
);
}
}
private __gc(): void {
if (isManaged<T>()) {
let buffer = this.buffer_;
let offset: usize = 0;
let end = <usize>this.length_ << alignof<usize>();
while (offset < end) {
__gc_mark(load<usize>(changetype<usize>(buffer) + offset, HEADER_SIZE)); // tslint:disable-line
offset += sizeof<usize>();
}
}
}
}

View File

@ -1,7 +1,7 @@
import {
HEADER_SIZE,
MAX_BLENGTH,
allocUnsafe
allocateUnsafe
} from "./internal/arraybuffer";
@sealed
@ -11,7 +11,7 @@ export class ArrayBuffer {
constructor(length: i32, unsafe: bool = false) {
if (<u32>length > <u32>MAX_BLENGTH) throw new RangeError("Invalid array buffer length");
var buffer = allocUnsafe(length);
var buffer = allocateUnsafe(length);
if (!unsafe) memory.fill(changetype<usize>(buffer) + HEADER_SIZE, 0, <usize>length);
return buffer;
}
@ -23,7 +23,7 @@ export class ArrayBuffer {
if (end < 0) end = max(len + end, 0);
else end = min(end, len);
var newLen = max(end - begin, 0);
var buffer = allocUnsafe(newLen);
var buffer = allocateUnsafe(newLen);
memory.copy(changetype<usize>(buffer) + HEADER_SIZE, changetype<usize>(this) + HEADER_SIZE + begin, newLen);
return buffer;
}

View File

@ -2,7 +2,6 @@
@builtin @inline export const NaN: f64 = 0 / 0;
@builtin @inline export const Infinity: f64 = 1 / 0;
@builtin export declare const HEAP_BASE: usize;
@builtin export declare function isInteger<T>(value?: T): bool;
@builtin export declare function isFloat<T>(value?: T): bool;
@ -12,6 +11,7 @@
@builtin export declare function isArray<T>(value?: T): bool;
@builtin export declare function isDefined(expression: void): bool;
@builtin export declare function isConstant(expression: void): bool;
@builtin export declare function isManaged<T>(value?: T): bool;
@inline export function isNaN<T>(value: T): bool { return value != value; }
@inline export function isFinite<T>(value: T): bool { return value - value == 0; }
@ -190,9 +190,3 @@ export namespace f64 {
}
@builtin export declare function start(): void;
@builtin export declare function ERROR(message?: void): void;
@builtin export declare function WARNING(message?: void): void;
@builtin export declare function INFO(message?: void): void;
@builtin export declare function __gc_iterate_roots(fn: (ref: usize) => void): void;

View File

@ -4,18 +4,15 @@
* @module std/assembly/collector/itcm
*//***/
// Largely based on the Bach Le's μgc, see: https://github.com/bullno1/ugc
// Largely based on Bach Le's μgc, see: https://github.com/bullno1/ugc
const TRACE = false;
import {
AL_MASK,
MAX_SIZE_32
} from "../internal/allocator";
/** Size of a managed object header. */
export const HEADER_SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
import {
iterateRoots
} from "../gc";
import { AL_MASK, MAX_SIZE_32 } from "../internal/allocator";
import { iterateRoots } from "../gc";
/** Collector states. */
const enum State {
@ -35,8 +32,8 @@ var state = State.INIT;
var white = 0;
// From and to spaces
var from: ManagedObjectList;
var to: ManagedObjectList;
var fromSpace: ManagedObjectList;
var toSpace: ManagedObjectList;
var iter: ManagedObject;
// ╒═══════════════ Managed object layout (32-bit) ════════════════╕
@ -47,15 +44,14 @@ var iter: ManagedObject;
// ├─────────────────────────────────────────────────────────┴─┴───┤ │ usize
// │ prev │ ◄─┘
// ├───────────────────────────────────────────────────────────────┤
// │ visitFn │
// │ hookFn
// ╞═══════════════════════════════════════════════════════════════╡ SIZE ┘ ◄─ user-space reference
// │ ... data ... │
// └───────────────────────────────────────────────────────────────┘
// C: color
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
@unmanaged
class ManagedObject {
@unmanaged class ManagedObject {
/** Pointer to the next object with color flags stored in the alignment bits. */
nextWithColor: usize;
@ -63,11 +59,8 @@ class ManagedObject {
/** Pointer to the previous object. */
prev: ManagedObject;
/** Visitor function called with the user-space reference. */
visitFn: (ref: usize) => void;
/** Size of a managed object after alignment. */
static readonly SIZE: usize = (offsetof<ManagedObject>() + AL_MASK) & ~AL_MASK;
/** Class-specific hook function called with the user-space reference. */
hookFn: (ref: usize) => void;
/** Gets the pointer to the next object. */
get next(): ManagedObject {
@ -104,14 +97,13 @@ class ManagedObject {
const gray = 2;
if (this == iter) iter = this.prev;
this.unlink();
to.push(this);
toSpace.push(this);
this.nextWithColor = (this.nextWithColor & ~3) | gray;
}
}
/** A list of managed objects. Used for the from and to spaces. */
@unmanaged
class ManagedObjectList extends ManagedObject {
@unmanaged class ManagedObjectList extends ManagedObject {
/** Inserts an object. */
push(obj: ManagedObject): void {
@ -137,13 +129,13 @@ function step(): void {
switch (state) {
case State.INIT: {
if (TRACE) trace("gc~step/INIT");
from = changetype<ManagedObjectList>(memory.allocate(ManagedObject.SIZE));
from.visitFn = changetype<(ref: usize) => void>(<u32>-1); // would error
from.clear();
to = changetype<ManagedObjectList>(memory.allocate(ManagedObject.SIZE));
to.visitFn = changetype<(ref: usize) => void>(<u32>-1); // would error
to.clear();
iter = to;
fromSpace = changetype<ManagedObjectList>(memory.allocate(HEADER_SIZE));
fromSpace.hookFn = changetype<(ref: usize) => void>(<u32>-1); // would error
fromSpace.clear();
toSpace = changetype<ManagedObjectList>(memory.allocate(HEADER_SIZE));
toSpace.hookFn = changetype<(ref: usize) => void>(<u32>-1); // would error
toSpace.clear();
iter = toSpace;
state = State.IDLE;
if (TRACE) trace("gc~state = IDLE");
// fall-through
@ -157,21 +149,21 @@ function step(): void {
}
case State.MARK: {
obj = iter.next;
if (obj !== to) {
if (obj !== toSpace) {
if (TRACE) trace("gc~step/MARK iterate", 1, objToRef(obj));
iter = obj;
obj.color = <i32>!white;
obj.visitFn(objToRef(obj));
obj.hookFn(objToRef(obj));
} else {
if (TRACE) trace("gc~step/MARK finish");
iterateRoots(__gc_mark);
obj = iter.next;
if (obj === to) {
let prevFrom = from;
from = to;
to = prevFrom;
if (obj === toSpace) {
let from = fromSpace;
fromSpace = toSpace;
toSpace = from;
white = <i32>!white;
iter = prevFrom.next;
iter = from.next;
state = State.SWEEP;
if (TRACE) trace("gc~state = SWEEP");
}
@ -180,13 +172,13 @@ function step(): void {
}
case State.SWEEP: {
obj = iter;
if (obj !== to) {
if (obj !== toSpace) {
if (TRACE) trace("gc~step/SWEEP free", 1, objToRef(obj));
iter = obj.next;
memory.free(changetype<usize>(obj));
if (changetype<usize>(obj) >= HEAP_BASE) memory.free(changetype<usize>(obj));
} else {
if (TRACE) trace("gc~step/SWEEP finish");
to.clear();
toSpace.clear();
state = State.IDLE;
if (TRACE) trace("gc~state = IDLE");
}
@ -196,26 +188,26 @@ function step(): void {
}
@inline function refToObj(ref: usize): ManagedObject {
return changetype<ManagedObject>(ref - ManagedObject.SIZE);
return changetype<ManagedObject>(ref - HEADER_SIZE);
}
@inline function objToRef(obj: ManagedObject): usize {
return changetype<usize>(obj) + ManagedObject.SIZE;
return changetype<usize>(obj) + HEADER_SIZE;
}
// Garbage collector interface
@global export function __gc_allocate(
size: usize,
visitFn: (ref: usize) => void
markFn: (ref: usize) => void
): usize {
if (TRACE) trace("gc.allocate", 1, size);
if (size > MAX_SIZE_32 - ManagedObject.SIZE) unreachable();
if (size > MAX_SIZE_32 - HEADER_SIZE) unreachable();
step(); // also makes sure it's initialized
var obj = changetype<ManagedObject>(memory.allocate(ManagedObject.SIZE + size));
obj.visitFn = visitFn;
var obj = changetype<ManagedObject>(memory.allocate(HEADER_SIZE + size));
obj.hookFn = markFn;
obj.color = white;
from.push(obj);
fromSpace.push(obj);
return objToRef(obj);
}

View File

@ -0,0 +1,5 @@
/* tslint:disable */
@builtin export declare function ERROR(message?: void): void;
@builtin export declare function WARNING(message?: void): void;
@builtin export declare function INFO(message?: void): void;

View File

@ -1,28 +1,12 @@
@builtin export declare function iterateRoots(fn: (ref: usize) => void): void; // tslint:disable-line
/* tslint:disable */
@builtin export declare function iterateRoots(fn: (ref: usize) => void): void;
export namespace gc {
export function allocate(size: usize, visitFn: (ref: usize) => void): usize {
if (isDefined(__gc_allocate)) return __gc_allocate(size, visitFn); // tslint:disable-line
WARNING("Calling 'gc.allocate' requires a garbage collector to be present.");
return <usize>unreachable();
}
export function collect(): void {
if (isDefined(__gc_collect)) { __gc_collect(); return; } // tslint:disable-line
if (isDefined(__gc_collect)) { __gc_collect(); return; }
WARNING("Calling 'gc.collect' requires a garbage collector to be present.");
unreachable();
}
export function link(parentRef: usize, childRef: usize): void {
if (isDefined(__gc_link)) { __gc_link(parentRef, childRef); return; } // tslint:disable-line
WARNING("Calling 'gc.link' requires a garbage collector to be present.");
unreachable();
}
export function mark(ref: usize): void {
if (isDefined(__gc_mark)) { __gc_mark(ref); return; } // tslint:disable-line
WARNING("Calling 'gc.mark' requires a garbage collector to be present.");
unreachable();
}
}

View File

@ -132,6 +132,8 @@ declare function isArray<T>(value?: any): value is Array<any>;
declare function isDefined(expression: any): bool;
/** Tests if the specified expression evaluates to a constant value. Compiles to a constant. */
declare function isConstant(expression: any): bool;
/** Tests if the specified type *or* expression is of a managed type. Compiles to a constant. */
declare function isManaged<T>(value?: any): bool;
/** Traps if the specified value is not true-ish, otherwise returns the (non-nullable) value. */
declare function assert<T>(isTrueish: T, message?: string): T & object; // any better way to model `: T != null`?
/** Parses an integer string to a 64-bit float. */
@ -354,10 +356,6 @@ declare namespace gc {
export function allocate(size: usize, visitFn: (ref: usize) => void): usize;
/** Performs a full garbage collection cycle. */
export function collect(): void;
/** Must be called when a managed object becomes a child of another one. */
export function link(parentRef: usize, childRef: usize): void;
/** Must be called when a managed object is found reachable. */
export function mark(ref: usize): void;
}
/** Table operations. */

View File

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

View File

@ -1,19 +1,12 @@
import {
loadUnsafe,
storeUnsafe
} from "./arraybuffer";
import { loadUnsafe, storeUnsafe } from "./arraybuffer";
import { Array } from "../array";
import {
Array
} from "../array";
/** Obtains the default comparator for the specified type. */
@inline
export function defaultComparator<T>(): (a: T, b: T) => i32 {
return (a: T, b: T): i32 => (<i32>(a > b) - <i32>(a < b)); // compiles to a constant table index
return function compare(a: T, b: T): i32 {
return (<i32>(a > b) - <i32>(a < b));
};
}
/** Sorts an Array with the 'Insertion Sort' algorithm. */
export function insertionSort<T>(arr: Array<T>, comparator: (a: T, b: T) => i32): Array<T> {
var buffer = arr.buffer_;
for (let i: i32 = 0, length: i32 = arr.length; i < length; i++) {
@ -30,7 +23,6 @@ export function insertionSort<T>(arr: Array<T>, comparator: (a: T, b: T) => i32)
return arr;
}
/** Sorts an Array with the 'Weak Heap Sort' algorithm. */
export function weakHeapSort<T>(arr: Array<T>, comparator: (a: T, b: T) => i32): Array<T> {
const shift32 = alignof<u32>();

View File

@ -2,12 +2,10 @@ import { AL_MASK, MAX_SIZE_32 } from "./allocator";
/** Size of an ArrayBuffer header. */
export const HEADER_SIZE: usize = (offsetof<ArrayBuffer>() + AL_MASK) & ~AL_MASK;
/** Maximum byte length of an ArrayBuffer. */
export const MAX_BLENGTH: i32 = <i32>MAX_SIZE_32 - HEADER_SIZE;
/** Computes an ArrayBuffer's size in memory. */
export function computeSize(byteLength: i32): usize {
function computeSize(byteLength: i32): usize {
// round up to power of 2, with HEADER_SIZE=8:
// 0 -> 2^3 = 8
// 1..8 -> 2^4 = 16
@ -17,16 +15,23 @@ export function computeSize(byteLength: i32): usize {
return <usize>1 << <usize>(<u32>32 - clz<u32>(byteLength + HEADER_SIZE - 1));
}
/** Allocates a raw ArrayBuffer. Contents remain uninitialized. */
export function allocUnsafe(byteLength: i32): ArrayBuffer {
// Low-level utility
function __gc(ref: usize): void {}
export function allocateUnsafe(byteLength: i32): ArrayBuffer {
assert(<u32>byteLength <= <u32>MAX_BLENGTH);
var buffer = memory.allocate(computeSize(byteLength));
var buffer: usize;
if (isManaged<ArrayBuffer>()) {
buffer = __gc_allocate(computeSize(byteLength), __gc); // tslint:disable-line
} else {
buffer = memory.allocate(computeSize(byteLength));
}
store<i32>(buffer, byteLength, offsetof<ArrayBuffer>("byteLength"));
return changetype<ArrayBuffer>(buffer);
}
/** Reallocates an ArrayBuffer, resizing it as requested. Tries to modify the buffer in place. */
export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuffer {
export function reallocateUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuffer {
var oldByteLength = buffer.byteLength;
if (newByteLength > oldByteLength) {
assert(newByteLength <= MAX_BLENGTH);
@ -38,7 +43,7 @@ export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuf
<usize>(newByteLength - oldByteLength)
);
} else { // slow path: copy to new buffer
let newBuffer = allocUnsafe(newByteLength);
let newBuffer = allocateUnsafe(newByteLength);
memory.copy(
changetype<usize>(newBuffer) + HEADER_SIZE,
changetype<usize>(buffer) + HEADER_SIZE,
@ -59,22 +64,18 @@ export function reallocUnsafe(buffer: ArrayBuffer, newByteLength: i32): ArrayBuf
return buffer;
}
@inline
export function loadUnsafe<T,V>(buffer: ArrayBuffer, index: i32): V {
@inline export function loadUnsafe<T,V>(buffer: ArrayBuffer, index: i32): V {
return <V>load<T>(changetype<usize>(buffer) + (<usize>index << alignof<T>()), HEADER_SIZE);
}
@inline
export function storeUnsafe<T,V>(buffer: ArrayBuffer, index: i32, value: V): void {
@inline export function storeUnsafe<T,V>(buffer: ArrayBuffer, index: i32, value: V): void {
store<T>(changetype<usize>(buffer) + (<usize>index << alignof<T>()), value, HEADER_SIZE);
}
@inline
export function loadUnsafeWithOffset<T,V>(buffer: ArrayBuffer, index: i32, byteOffset: i32): V {
@inline export function loadUnsafeWithOffset<T,V>(buffer: ArrayBuffer, index: i32, byteOffset: i32): V {
return <V>load<T>(changetype<usize>(buffer) + <usize>byteOffset + (<usize>index << alignof<T>()), HEADER_SIZE);
}
@inline
export function storeUnsafeWithOffset<T,V>(buffer: ArrayBuffer, index: i32, value: V, byteOffset: i32): void {
@inline export function storeUnsafeWithOffset<T,V>(buffer: ArrayBuffer, index: i32, value: V, byteOffset: i32): void {
store<T>(changetype<usize>(buffer) + <usize>byteOffset + (<usize>index << alignof<T>()), value, HEADER_SIZE);
}

View File

@ -1,5 +1,5 @@
import {
HEADER_SIZE as HEADER_SIZE_STR
HEADER_SIZE as STRING_HEADER_SIZE
} from "./string";
/** Computes the 32-bit hash of a value of any type. */
@ -66,7 +66,7 @@ function hash64(key: u64): u32 {
function hashStr(key: string): u32 {
var v = FNV_OFFSET;
for (let i: usize = 0, k: usize = key.length << 1; i < k; ++i) {
v = (v ^ <u32>load<u8>(changetype<usize>(key) + i, HEADER_SIZE_STR)) * FNV_PRIME;
v = (v ^ <u32>load<u8>(changetype<usize>(key) + i, STRING_HEADER_SIZE)) * FNV_PRIME;
}
return v;
}

View File

@ -1,7 +1,7 @@
import {
CharCode,
allocate as allocateString,
allocateUnsafe as allocateUnsafeString,
HEADER_SIZE as STRING_HEADER_SIZE
} from "./string";
@ -194,7 +194,7 @@ export function utoa32(value: u32): string {
if (!value) return "0";
var decimals = decimalCountU32(value);
var buffer = allocateString(decimals);
var buffer = allocateUnsafeString(decimals);
utoa32_core(changetype<usize>(buffer), value, decimals);
return changetype<string>(buffer);
@ -207,7 +207,7 @@ export function itoa32(value: i32): string {
if (isneg) value = -value;
var decimals = decimalCountU32(value) + <i32>isneg;
var buffer = allocateString(decimals);
var buffer = allocateUnsafeString(decimals);
utoa32_core(changetype<usize>(buffer), value, decimals);
if (isneg) store<u16>(changetype<usize>(buffer), CharCode.MINUS, STRING_HEADER_SIZE);
@ -222,11 +222,11 @@ export function utoa64(value: u64): string {
if (value <= u32.MAX_VALUE) {
let value32 = <u32>value;
let decimals = decimalCountU32(value32);
buffer = allocateString(decimals);
buffer = allocateUnsafeString(decimals);
utoa32_core(changetype<usize>(buffer), value32, decimals);
} else {
let decimals = decimalCountU64(value);
buffer = allocateString(decimals);
buffer = allocateUnsafeString(decimals);
utoa64_core(changetype<usize>(buffer), value, decimals);
}
@ -243,11 +243,11 @@ export function itoa64(value: i64): string {
if (<u64>value <= <u64>u32.MAX_VALUE) {
let value32 = <u32>value;
let decimals = decimalCountU32(value32) + <i32>isneg;
buffer = allocateString(decimals);
buffer = allocateUnsafeString(decimals);
utoa32_core(changetype<usize>(buffer), value32, decimals);
} else {
let decimals = decimalCountU64(value) + <i32>isneg;
buffer = allocateString(decimals);
buffer = allocateUnsafeString(decimals);
utoa64_core(changetype<usize>(buffer), value, decimals);
}
if (isneg) store<u16>(changetype<usize>(buffer), CharCode.MINUS, STRING_HEADER_SIZE);

View File

@ -0,0 +1,254 @@
// this function will go away once `memory.copy` becomes an intrinsic
export function memcpy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memcpy.c
var w: u32, x: u32;
// copy 1 byte each until src is aligned to 4 bytes
while (n && (src & 3)) {
store<u8>(dest++, load<u8>(src++));
n--;
}
// if dst is aligned to 4 bytes as well, copy 4 bytes each
if ((dest & 3) == 0) {
while (n >= 16) {
store<u32>(dest , load<u32>(src ));
store<u32>(dest + 4, load<u32>(src + 4));
store<u32>(dest + 8, load<u32>(src + 8));
store<u32>(dest + 12, load<u32>(src + 12));
src += 16; dest += 16; n -= 16;
}
if (n & 8) {
store<u32>(dest , load<u32>(src ));
store<u32>(dest + 4, load<u32>(src + 4));
dest += 8; src += 8;
}
if (n & 4) {
store<u32>(dest, load<u32>(src));
dest += 4; src += 4;
}
if (n & 2) { // drop to 2 bytes each
store<u16>(dest, load<u16>(src));
dest += 2; src += 2;
}
if (n & 1) { // drop to 1 byte
store<u8>(dest++, load<u8>(src++));
}
return;
}
// if dst is not aligned to 4 bytes, use alternating shifts to copy 4 bytes each
// doing shifts if faster when copying enough bytes (here: 32 or more)
if (n >= 32) {
switch (dest & 3) {
// known to be != 0
case 1: {
w = load<u32>(src);
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
n -= 3;
while (n >= 17) {
x = load<u32>(src + 1);
store<u32>(dest, w >> 24 | x << 8);
w = load<u32>(src + 5);
store<u32>(dest + 4, x >> 24 | w << 8);
x = load<u32>(src + 9);
store<u32>(dest + 8, w >> 24 | x << 8);
w = load<u32>(src + 13);
store<u32>(dest + 12, x >> 24 | w << 8);
src += 16; dest += 16; n -= 16;
}
break;
}
case 2: {
w = load<u32>(src);
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
n -= 2;
while (n >= 18) {
x = load<u32>(src + 2);
store<u32>(dest, w >> 16 | x << 16);
w = load<u32>(src + 6);
store<u32>(dest + 4, x >> 16 | w << 16);
x = load<u32>(src + 10);
store<u32>(dest + 8, w >> 16 | x << 16);
w = load<u32>(src + 14);
store<u32>(dest + 12, x >> 16 | w << 16);
src += 16; dest += 16; n -= 16;
}
break;
}
case 3: {
w = load<u32>(src);
store<u8>(dest++, load<u8>(src++));
n -= 1;
while (n >= 19) {
x = load<u32>(src + 3);
store<u32>(dest, w >> 8 | x << 24);
w = load<u32>(src + 7);
store<u32>(dest + 4, x >> 8 | w << 24);
x = load<u32>(src + 11);
store<u32>(dest + 8, w >> 8 | x << 24);
w = load<u32>(src + 15);
store<u32>(dest + 12, x >> 8 | w << 24);
src += 16; dest += 16; n -= 16;
}
break;
}
}
}
// copy remaining bytes one by one
if (n & 16) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 8) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 4) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 2) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 1) {
store<u8>(dest++, load<u8>(src++));
}
}
// this function will go away once `memory.copy` becomes an intrinsic
export function memmove(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c
if (dest == src) return;
if (src + n <= dest || dest + n <= src) {
memcpy(dest, src, n);
return;
}
if (dest < src) {
if ((src & 7) == (dest & 7)) {
while (dest & 7) {
if (!n) return;
--n;
store<u8>(dest++, load<u8>(src++));
}
while (n >= 8) {
store<u64>(dest, load<u64>(src));
n -= 8;
dest += 8;
src += 8;
}
}
while (n) {
store<u8>(dest++, load<u8>(src++));
--n;
}
} else {
if ((src & 7) == (dest & 7)) {
while ((dest + n) & 7) {
if (!n) return;
store<u8>(dest + --n, load<u8>(src + n));
}
while (n >= 8) {
n -= 8;
store<u64>(dest + n, load<u64>(src + n));
}
}
while (n) {
store<u8>(dest + --n, load<u8>(src + n));
}
}
}
// this function will go away once `memory.fill` becomes an intrinsic
export function memset(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset
// fill head and tail with minimal branching
if (!n) return;
store<u8>(dest, c);
store<u8>(dest + n - 1, c);
if (n <= 2) return;
store<u8>(dest + 1, c);
store<u8>(dest + 2, c);
store<u8>(dest + n - 2, c);
store<u8>(dest + n - 3, c);
if (n <= 6) return;
store<u8>(dest + 3, c);
store<u8>(dest + n - 4, c);
if (n <= 8) return;
// advance pointer to align it at 4-byte boundary
var k: usize = -dest & 3;
dest += k;
n -= k;
n &= -4;
var c32: u32 = <u32>-1 / 255 * c;
// fill head/tail up to 28 bytes each in preparation
store<u32>(dest, c32);
store<u32>(dest + n - 4, c32);
if (n <= 8) return;
store<u32>(dest + 4, c32);
store<u32>(dest + 8, c32);
store<u32>(dest + n - 12, c32);
store<u32>(dest + n - 8, c32);
if (n <= 24) return;
store<u32>(dest + 12, c32);
store<u32>(dest + 16, c32);
store<u32>(dest + 20, c32);
store<u32>(dest + 24, c32);
store<u32>(dest + n - 28, c32);
store<u32>(dest + n - 24, c32);
store<u32>(dest + n - 20, c32);
store<u32>(dest + n - 16, c32);
// align to a multiple of 8
k = 24 + (dest & 4);
dest += k;
n -= k;
// copy 32 bytes each
var c64: u64 = <u64>c32 | (<u64>c32 << 32);
while (n >= 32) {
store<u64>(dest, c64);
store<u64>(dest + 8, c64);
store<u64>(dest + 16, c64);
store<u64>(dest + 24, c64);
n -= 32;
dest += 32;
}
}
export function memcmp(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c
if (vl == vr) return 0;
while (n != 0 && load<u8>(vl) == load<u8>(vr)) {
n--; vl++; vr++;
}
return n ? <i32>load<u8>(vl) - <i32>load<u8>(vr) : 0;
}

View File

@ -1,34 +1,27 @@
import {
MAX_SIZE_32
} from "./allocator";
import {
String
} from "../string";
import { MAX_SIZE_32 } from "./allocator";
import { String } from "../string";
/** Size of a String header. */
export const HEADER_SIZE = (offsetof<String>() + 1) & ~1; // 2 byte aligned
/** Maximum length of a String. */
export const MAX_LENGTH = (<i32>MAX_SIZE_32 - HEADER_SIZE) >>> 1;
/** Singleton empty String. */
export const EMPTY = changetype<String>(""); // TODO: is this a bad idea with '===' in place?
// Low-level utility
@inline
export function clamp<T>(val: T, lo: T, hi: T): T {
return min<T>(max<T>(val, lo), hi);
}
function __gc(ref: usize): void {}
/** Allocates a raw String with uninitialized contents. */
export function allocate(length: i32): String {
export function allocateUnsafe(length: i32): String {
assert(length > 0 && length <= MAX_LENGTH);
var buffer = memory.allocate(HEADER_SIZE + (<usize>length << 1));
var buffer: usize;
if (isManaged<String>()) {
buffer = __gc_allocate(HEADER_SIZE + (<usize>length << 1), __gc); // tslint:disable-line
} else {
buffer = memory.allocate(HEADER_SIZE + (<usize>length << 1));
}
store<i32>(buffer, length);
return changetype<String>(buffer);
}
@inline
export function copyUnsafe(dest: String, destOffset: usize, src: String, srcOffset: usize, len: usize): void {
memory.copy(
changetype<usize>(dest) + (destOffset << 1) + HEADER_SIZE,
@ -37,120 +30,6 @@ export function copyUnsafe(dest: String, destOffset: usize, src: String, srcOffs
);
}
export function isWhiteSpaceOrLineTerminator(c: u16): bool {
switch (c) {
case 10: // <LF>
case 13: // <CR>
case 8232: // <LS>
case 8233: // <PS>
case 9: // <TAB>
case 11: // <VT>
case 12: // <FF>
case 32: // <SP>
case 160: // <NBSP>
case 65279: { // <ZWNBSP>
return true;
}
default: return false;
}
}
export const enum CharCode {
PLUS = 0x2B,
MINUS = 0x2D,
DOT = 0x2E,
_0 = 0x30,
_1 = 0x31,
_2 = 0x32,
_3 = 0x33,
_4 = 0x34,
_5 = 0x35,
_6 = 0x36,
_7 = 0x37,
_8 = 0x38,
_9 = 0x39,
A = 0x41,
B = 0x42,
E = 0x45,
O = 0x4F,
X = 0x58,
Z = 0x5a,
a = 0x61,
b = 0x62,
e = 0x65,
o = 0x6F,
x = 0x78,
z = 0x7A
}
export function parse<T>(str: String, radix: i32 = 0): T {
var len: i32 = str.length;
if (!len) return <T>NaN;
var ptr = changetype<usize>(str) /* + HEAD -> offset */;
var code = <i32>load<u16>(ptr, HEADER_SIZE);
// determine sign
var sign: T;
if (code == CharCode.MINUS) {
if (!--len) return <T>NaN;
code = <i32>load<u16>(ptr += 2, HEADER_SIZE);
sign = -1;
} else if (code == CharCode.PLUS) {
if (!--len) return <T>NaN;
code = <i32>load<u16>(ptr += 2, HEADER_SIZE);
sign = 1;
} else {
sign = 1;
}
// determine radix
if (!radix) {
if (code == CharCode._0 && len > 2) {
switch (<i32>load<u16>(ptr + 2, HEADER_SIZE)) {
case CharCode.B:
case CharCode.b: {
ptr += 4; len -= 2;
radix = 2;
break;
}
case CharCode.O:
case CharCode.o: {
ptr += 4; len -= 2;
radix = 8;
break;
}
case CharCode.X:
case CharCode.x: {
ptr += 4; len -= 2;
radix = 16;
break;
}
default: radix = 10;
}
} else radix = 10;
} else if (radix < 2 || radix > 36) {
return <T>NaN;
}
// calculate value
var num: T = 0;
while (len--) {
code = <i32>load<u16>(ptr, HEADER_SIZE);
if (code >= CharCode._0 && code <= CharCode._9) {
code -= CharCode._0;
} else if (code >= CharCode.A && code <= CharCode.Z) {
code -= CharCode.A - 10;
} else if (code >= CharCode.a && code <= CharCode.z) {
code -= CharCode.a - 10;
} else break;
if (code >= radix) break;
num = (num * radix) + code;
ptr += 2;
}
return sign * num;
}
export function compareUnsafe(str1: String, offset1: usize, str2: String, offset2: usize, len: usize): i32 {
var cmp: i32 = 0;
var ptr1 = changetype<usize>(str1) + (offset1 << 1);
@ -219,3 +98,118 @@ export function repeatUnsafe(dest: String, destOffset: usize, src: String, count
}
}
}
// Helpers
export const enum CharCode {
PLUS = 0x2B,
MINUS = 0x2D,
DOT = 0x2E,
_0 = 0x30,
_1 = 0x31,
_2 = 0x32,
_3 = 0x33,
_4 = 0x34,
_5 = 0x35,
_6 = 0x36,
_7 = 0x37,
_8 = 0x38,
_9 = 0x39,
A = 0x41,
B = 0x42,
E = 0x45,
O = 0x4F,
X = 0x58,
Z = 0x5a,
a = 0x61,
b = 0x62,
e = 0x65,
o = 0x6F,
x = 0x78,
z = 0x7A
}
export function isWhiteSpaceOrLineTerminator(c: u16): bool {
switch (c) {
case 10: // <LF>
case 13: // <CR>
case 8232: // <LS>
case 8233: // <PS>
case 9: // <TAB>
case 11: // <VT>
case 12: // <FF>
case 32: // <SP>
case 160: // <NBSP>
case 65279: return true; // <ZWNBSP>
default: return false;
}
}
/** Parses a string to an integer (usually), using the specified radix. */
export function parse<T>(str: String, radix: i32 = 0): T {
var len: i32 = str.length;
if (!len) return <T>NaN;
var ptr = changetype<usize>(str) /* + HEAD -> offset */;
var code = <i32>load<u16>(ptr, HEADER_SIZE);
// determine sign
var sign: T;
if (code == CharCode.MINUS) {
if (!--len) return <T>NaN;
code = <i32>load<u16>(ptr += 2, HEADER_SIZE);
sign = -1;
} else if (code == CharCode.PLUS) {
if (!--len) return <T>NaN;
code = <i32>load<u16>(ptr += 2, HEADER_SIZE);
sign = 1;
} else {
sign = 1;
}
// determine radix
if (!radix) {
if (code == CharCode._0 && len > 2) {
switch (<i32>load<u16>(ptr + 2, HEADER_SIZE)) {
case CharCode.B:
case CharCode.b: {
ptr += 4; len -= 2;
radix = 2;
break;
}
case CharCode.O:
case CharCode.o: {
ptr += 4; len -= 2;
radix = 8;
break;
}
case CharCode.X:
case CharCode.x: {
ptr += 4; len -= 2;
radix = 16;
break;
}
default: radix = 10;
}
} else radix = 10;
} else if (radix < 2 || radix > 36) {
return <T>NaN;
}
// calculate value
var num: T = 0;
while (len--) {
code = <i32>load<u16>(ptr, HEADER_SIZE);
if (code >= CharCode._0 && code <= CharCode._9) {
code -= CharCode._0;
} else if (code >= CharCode.A && code <= CharCode.Z) {
code -= CharCode.A - 10;
} else if (code >= CharCode.a && code <= CharCode.z) {
code -= CharCode.a - 10;
} else break;
if (code >= radix) break;
num = (num * radix) + code;
ptr += 2;
}
return sign * num;
}

View File

@ -1,7 +1,7 @@
import {
HEADER_SIZE as HEADER_SIZE_AB,
MAX_BLENGTH,
allocUnsafe,
HEADER_SIZE as AB_HEADER_SIZE,
MAX_BLENGTH as AB_MAX_BLENGTH,
allocateUnsafe,
loadUnsafeWithOffset,
storeUnsafeWithOffset
} from "./arraybuffer";
@ -14,11 +14,11 @@ export abstract class TypedArray<T,V> {
readonly byteLength: i32;
constructor(length: i32) {
const MAX_LENGTH = <u32>MAX_BLENGTH / sizeof<T>();
const MAX_LENGTH = <u32>AB_MAX_BLENGTH / sizeof<T>();
if (<u32>length > MAX_LENGTH) throw new RangeError("Invalid typed array length");
var byteLength = length << alignof<T>();
var buffer = allocUnsafe(byteLength);
memory.fill(changetype<usize>(buffer) + HEADER_SIZE_AB, 0, <usize>byteLength);
var buffer = allocateUnsafe(byteLength);
memory.fill(changetype<usize>(buffer) + AB_HEADER_SIZE, 0, <usize>byteLength);
this.buffer = buffer;
this.byteOffset = 0;
this.byteLength = byteLength;

View File

@ -114,6 +114,8 @@ export class Map<K,V> {
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
entry.taggedNext = load<usize>(bucketPtrBase, HEADER_SIZE_AB);
store<usize>(bucketPtrBase, changetype<usize>(entry), HEADER_SIZE_AB);
if (isManaged<K>()) __gc_link(changetype<usize>(this), changetype<usize>(key)); // tslint:disable-line
if (isManaged<V>()) __gc_link(changetype<usize>(this), changetype<usize>(value)); // tslint:disable-line
}
}
@ -162,4 +164,22 @@ export class Map<K,V> {
this.entriesCapacity = newEntriesCapacity;
this.entriesOffset = this.entriesCount;
}
private __gc(): void {
if (isManaged<K>() || isManaged<V>()) {
let entries = this.entries;
let offset: usize = 0;
let end: usize = this.entriesOffset * ENTRY_SIZE<K,V>();
while (offset < end) {
let entry = changetype<MapEntry<K,V>>(
changetype<usize>(entries) + HEADER_SIZE_AB + offset * ENTRY_SIZE<K,V>()
);
if (!(entry.taggedNext & EMPTY)) {
if (isManaged<K>()) __gc_mark(changetype<usize>(entry.key)); // tslint:disable-line
if (isManaged<V>()) __gc_mark(changetype<usize>(entry.value)); // tslint:disable-line
}
offset += ENTRY_SIZE<K,V>();
}
}
}
}

View File

@ -1,21 +1,27 @@
import { memcmp, memmove, memset } from "./internal/memory";
@builtin export declare const HEAP_BASE: usize; // tslint:disable-line
/* tslint:disable */
export namespace memory {
@builtin export declare function size(): i32; // tslint:disable-line
@builtin export declare function size(): i32;
@builtin export declare function grow(pages: i32): i32; // tslint:disable-line
@builtin export declare function grow(pages: i32): i32;
export function fill(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset
if (isDefined(__memory_fill)) { __memory_fill(dest, c, n); return; } // tslint:disable-line
@inline export function fill(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset
if (isDefined(__memory_fill)) { __memory_fill(dest, c, n); return; }
memset(dest, c, n);
}
export function copy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c
if (isDefined(__memory_copy)) { __memory_copy(dest, src, n); return; } // tslint:disable-line
@inline export function copy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c
if (isDefined(__memory_copy)) { __memory_copy(dest, src, n); return; }
memmove(dest, src, n);
}
export function compare(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c
if (isDefined(__memory_compare)) return __memory_compare(vl, vr, n); // tslint:disable-line
@inline export function compare(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c
if (isDefined(__memory_compare)) return __memory_compare(vl, vr, n);
return memcmp(vl, vr, n);
}
@ -31,275 +37,20 @@ export namespace memory {
// Allocator
export function allocate(size: usize): usize {
if (isDefined(__memory_allocate)) return __memory_allocate(size); // tslint:disable-line
@inline export function allocate(size: usize): usize {
if (isDefined(__memory_allocate)) return __memory_allocate(size);
WARNING("Calling 'memory.allocate' requires a memory manager to be present.");
return <usize>unreachable();
}
export function free(ptr: usize): void {
if (isDefined(__memory_free)) { __memory_free(ptr); return; } // tslint:disable-line
@inline export function free(ptr: usize): void {
if (isDefined(__memory_free)) { __memory_free(ptr); return; }
WARNING("Calling 'memory.free' requires a memory manager to be present.");
unreachable();
}
export function reset(): void {
if (isDefined(__memory_reset)) { __memory_reset(); return; } // tslint:disable-line
@inline export function reset(): void {
if (isDefined(__memory_reset)) { __memory_reset(); return; }
unreachable();
}
}
// this function will go away once `memory.copy` becomes an intrinsic
function memcpy(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memcpy.c
var w: u32, x: u32;
// copy 1 byte each until src is aligned to 4 bytes
while (n && (src & 3)) {
store<u8>(dest++, load<u8>(src++));
n--;
}
// if dst is aligned to 4 bytes as well, copy 4 bytes each
if ((dest & 3) == 0) {
while (n >= 16) {
store<u32>(dest , load<u32>(src ));
store<u32>(dest + 4, load<u32>(src + 4));
store<u32>(dest + 8, load<u32>(src + 8));
store<u32>(dest + 12, load<u32>(src + 12));
src += 16; dest += 16; n -= 16;
}
if (n & 8) {
store<u32>(dest , load<u32>(src ));
store<u32>(dest + 4, load<u32>(src + 4));
dest += 8; src += 8;
}
if (n & 4) {
store<u32>(dest, load<u32>(src));
dest += 4; src += 4;
}
if (n & 2) { // drop to 2 bytes each
store<u16>(dest, load<u16>(src));
dest += 2; src += 2;
}
if (n & 1) { // drop to 1 byte
store<u8>(dest++, load<u8>(src++));
}
return;
}
// if dst is not aligned to 4 bytes, use alternating shifts to copy 4 bytes each
// doing shifts if faster when copying enough bytes (here: 32 or more)
if (n >= 32) {
switch (dest & 3) {
// known to be != 0
case 1: {
w = load<u32>(src);
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
n -= 3;
while (n >= 17) {
x = load<u32>(src + 1);
store<u32>(dest, w >> 24 | x << 8);
w = load<u32>(src + 5);
store<u32>(dest + 4, x >> 24 | w << 8);
x = load<u32>(src + 9);
store<u32>(dest + 8, w >> 24 | x << 8);
w = load<u32>(src + 13);
store<u32>(dest + 12, x >> 24 | w << 8);
src += 16; dest += 16; n -= 16;
}
break;
}
case 2: {
w = load<u32>(src);
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
n -= 2;
while (n >= 18) {
x = load<u32>(src + 2);
store<u32>(dest, w >> 16 | x << 16);
w = load<u32>(src + 6);
store<u32>(dest + 4, x >> 16 | w << 16);
x = load<u32>(src + 10);
store<u32>(dest + 8, w >> 16 | x << 16);
w = load<u32>(src + 14);
store<u32>(dest + 12, x >> 16 | w << 16);
src += 16; dest += 16; n -= 16;
}
break;
}
case 3: {
w = load<u32>(src);
store<u8>(dest++, load<u8>(src++));
n -= 1;
while (n >= 19) {
x = load<u32>(src + 3);
store<u32>(dest, w >> 8 | x << 24);
w = load<u32>(src + 7);
store<u32>(dest + 4, x >> 8 | w << 24);
x = load<u32>(src + 11);
store<u32>(dest + 8, w >> 8 | x << 24);
w = load<u32>(src + 15);
store<u32>(dest + 12, x >> 8 | w << 24);
src += 16; dest += 16; n -= 16;
}
break;
}
}
}
// copy remaining bytes one by one
if (n & 16) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 8) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 4) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 2) {
store<u8>(dest++, load<u8>(src++));
store<u8>(dest++, load<u8>(src++));
}
if (n & 1) {
store<u8>(dest++, load<u8>(src++));
}
}
// this function will go away once `memory.copy` becomes an intrinsic
function memmove(dest: usize, src: usize, n: usize): void { // see: musl/src/string/memmove.c
if (dest == src) return;
if (src + n <= dest || dest + n <= src) {
memcpy(dest, src, n);
return;
}
if (dest < src) {
if ((src & 7) == (dest & 7)) {
while (dest & 7) {
if (!n) return;
--n;
store<u8>(dest++, load<u8>(src++));
}
while (n >= 8) {
store<u64>(dest, load<u64>(src));
n -= 8;
dest += 8;
src += 8;
}
}
while (n) {
store<u8>(dest++, load<u8>(src++));
--n;
}
} else {
if ((src & 7) == (dest & 7)) {
while ((dest + n) & 7) {
if (!n) return;
store<u8>(dest + --n, load<u8>(src + n));
}
while (n >= 8) {
n -= 8;
store<u64>(dest + n, load<u64>(src + n));
}
}
while (n) {
store<u8>(dest + --n, load<u8>(src + n));
}
}
}
// this function will go away once `memory.fill` becomes an intrinsic
function memset(dest: usize, c: u8, n: usize): void { // see: musl/src/string/memset
// fill head and tail with minimal branching
if (!n) return;
store<u8>(dest, c);
store<u8>(dest + n - 1, c);
if (n <= 2) return;
store<u8>(dest + 1, c);
store<u8>(dest + 2, c);
store<u8>(dest + n - 2, c);
store<u8>(dest + n - 3, c);
if (n <= 6) return;
store<u8>(dest + 3, c);
store<u8>(dest + n - 4, c);
if (n <= 8) return;
// advance pointer to align it at 4-byte boundary
var k: usize = -dest & 3;
dest += k;
n -= k;
n &= -4;
var c32: u32 = <u32>-1 / 255 * c;
// fill head/tail up to 28 bytes each in preparation
store<u32>(dest, c32);
store<u32>(dest + n - 4, c32);
if (n <= 8) return;
store<u32>(dest + 4, c32);
store<u32>(dest + 8, c32);
store<u32>(dest + n - 12, c32);
store<u32>(dest + n - 8, c32);
if (n <= 24) return;
store<u32>(dest + 12, c32);
store<u32>(dest + 16, c32);
store<u32>(dest + 20, c32);
store<u32>(dest + 24, c32);
store<u32>(dest + n - 28, c32);
store<u32>(dest + n - 24, c32);
store<u32>(dest + n - 20, c32);
store<u32>(dest + n - 16, c32);
// align to a multiple of 8
k = 24 + (dest & 4);
dest += k;
n -= k;
// copy 32 bytes each
var c64: u64 = <u64>c32 | (<u64>c32 << 32);
while (n >= 32) {
store<u64>(dest, c64);
store<u64>(dest + 8, c64);
store<u64>(dest + 16, c64);
store<u64>(dest + 24, c64);
n -= 32;
dest += 32;
}
}
function memcmp(vl: usize, vr: usize, n: usize): i32 { // see: musl/src/string/memcmp.c
if (vl == vr) return 0;
while (n != 0 && load<u8>(vl) == load<u8>(vr)) {
n--; vl++; vr++;
}
return n ? <i32>load<u8>(vl) - <i32>load<u8>(vr) : 0;
}

View File

@ -104,6 +104,7 @@ export class Set<K> {
let bucketPtrBase = changetype<usize>(this.buckets) + <usize>(hashCode & this.bucketsMask) * BUCKET_SIZE;
entry.taggedNext = load<usize>(bucketPtrBase, HEADER_SIZE_AB);
store<usize>(bucketPtrBase, changetype<usize>(entry), HEADER_SIZE_AB);
if (isManaged<K>()) __gc_link(changetype<usize>(this), changetype<usize>(key)); // tslint:disable-line
}
}
@ -151,4 +152,19 @@ export class Set<K> {
this.entriesCapacity = newEntriesCapacity;
this.entriesOffset = this.entriesCount;
}
private __gc(): void {
if (isManaged<K>()) {
let entries = this.entries;
let offset: usize = 0;
let end: usize = this.entriesOffset * ENTRY_SIZE<K>();
while (offset < end) {
let entry = changetype<SetEntry<K>>(
changetype<usize>(entries) + HEADER_SIZE_AB + offset * ENTRY_SIZE<K>()
);
if (!(entry.taggedNext & EMPTY)) __gc_mark(changetype<usize>(entry.key)); // tslint:disable-line
offset += ENTRY_SIZE<K>();
}
}
}
}

View File

@ -1,9 +1,7 @@
import {
HEADER_SIZE,
MAX_LENGTH,
EMPTY,
clamp,
allocate,
allocateUnsafe,
compareUnsafe,
repeatUnsafe,
copyUnsafe,
@ -20,7 +18,7 @@ export class String {
// TODO Add and handle second argument
static fromCharCode(code: i32): String {
if (!code) return changetype<String>("\0");
var out = allocate(1);
var out = allocateUnsafe(1);
store<u16>(
changetype<usize>(out),
<u16>code,
@ -33,7 +31,7 @@ export class String {
assert(<u32>code <= 0x10FFFF); // Invalid code point range
if (!code) return changetype<String>("\0");
var sur = code > 0xFFFF;
var out = allocate(<i32>sur + 1);
var out = allocateUnsafe(<i32>sur + 1);
if (!sur) {
store<u16>(
changetype<usize>(out),
@ -57,11 +55,9 @@ export class String {
charAt(pos: i32): String {
assert(this !== null);
if (<u32>pos >= <u32>this.length) {
return EMPTY;
}
if (<u32>pos >= <u32>this.length) return changetype<String>("");
var out = allocate(1);
var out = allocateUnsafe(1);
store<u16>(
changetype<usize>(out),
load<u16>(
@ -115,8 +111,8 @@ export class String {
var thisLen: isize = this.length;
var otherLen: isize = other.length;
var outLen: usize = thisLen + otherLen;
if (outLen == 0) return EMPTY;
var out = allocate(outLen);
if (outLen == 0) return changetype<String>("");
var out = allocateUnsafe(outLen);
copyUnsafe(out, 0, this, 0, thisLen);
copyUnsafe(out, thisLen, other, 0, otherLen);
return out;
@ -125,7 +121,7 @@ export class String {
endsWith(searchString: String, endPosition: i32 = MAX_LENGTH): bool {
assert(this !== null);
if (searchString === null) return false;
var end = clamp<isize>(endPosition, 0, this.length);
var end = min(max(endPosition, 0), this.length);
var searchLength: isize = searchString.length;
var start: isize = end - searchLength;
if (start < 0) return false;
@ -218,7 +214,7 @@ export class String {
if (!searchLen) return 0;
var len: isize = this.length;
if (!len) return -1;
var start = clamp<isize>(fromIndex, 0, len);
var start = min<isize>(max<isize>(fromIndex, 0), len);
len -= searchLen;
for (let k: isize = start; k <= len; ++k) {
if (!compareUnsafe(this, k, searchString, 0, searchLen)) return <i32>k;
@ -234,7 +230,7 @@ export class String {
var searchLen: isize = searchString.length;
if (!searchLen) return len;
if (!len) return -1;
var start = clamp<isize>(fromIndex, 0, len - searchLen);
var start = min<isize>(max(fromIndex, 0), len - searchLen);
for (let k = start; k >= 0; --k) {
if (!compareUnsafe(this, k, searchString, 0, searchLen)) return <i32>k;
}
@ -247,7 +243,7 @@ export class String {
var pos: isize = position;
var len: isize = this.length;
var start = clamp<isize>(pos, 0, len);
var start = min(max(pos, 0), len);
var searchLength: isize = searchString.length;
if (searchLength + start > len) return false;
return !compareUnsafe(this, start, searchString, 0, searchLength);
@ -258,10 +254,10 @@ export class String {
var intStart: isize = start;
var end: isize = length;
var size: isize = this.length;
if (intStart < 0) intStart = max<isize>(size + intStart, 0);
var resultLength = clamp<isize>(end, 0, size - intStart);
if (resultLength <= 0) return EMPTY;
var out = allocate(resultLength);
if (intStart < 0) intStart = max(size + intStart, 0);
var resultLength = min(max(end, 0), size - intStart);
if (resultLength <= 0) return changetype<String>("");
var out = allocateUnsafe(resultLength);
copyUnsafe(out, 0, this, intStart, resultLength);
return out;
}
@ -269,14 +265,14 @@ export class String {
substring(start: i32, end: i32 = i32.MAX_VALUE): String {
assert(this !== null);
var len = this.length;
var finalStart = clamp<isize>(start, 0, len);
var finalEnd = clamp<isize>(end, 0, len);
var finalStart = min(max(start, 0), len);
var finalEnd = min(max(end, 0), len);
var from = min<i32>(finalStart, finalEnd);
var to = max<i32>(finalStart, finalEnd);
len = to - from;
if (!len) return EMPTY;
if (!len) return changetype<String>("");
if (!from && to == this.length) return this;
var out = allocate(len);
var out = allocateUnsafe(len);
copyUnsafe(out, 0, this, from, len);
return out;
}
@ -302,9 +298,9 @@ export class String {
) {
++start, --length;
}
if (!length) return EMPTY;
if (!length) return changetype<String>("");
if (!start && length == this.length) return this;
var out = allocate(length);
var out = allocateUnsafe(length);
copyUnsafe(out, 0, this, start, length);
return out;
}
@ -323,8 +319,8 @@ export class String {
}
if (!start) return this;
var outLen = len - start;
if (!outLen) return EMPTY;
var out = allocate(outLen);
if (!outLen) return changetype<String>("");
var out = allocateUnsafe(outLen);
copyUnsafe(out, 0, this, start, outLen);
return out;
}
@ -340,9 +336,9 @@ export class String {
) {
--len;
}
if (len <= 0) return EMPTY;
if (len <= 0) return changetype<String>("");
if (<i32>len == this.length) return this;
var out = allocate(len);
var out = allocateUnsafe(len);
copyUnsafe(out, 0, this, 0, len);
return out;
}
@ -353,7 +349,7 @@ export class String {
var padLen = padString.length;
if (targetLength < length || !padLen) return this;
var len = targetLength - length;
var out = allocate(targetLength);
var out = allocateUnsafe(targetLength);
if (len > padLen) {
let count = (len - 1) / padLen;
let base = count * padLen;
@ -373,7 +369,7 @@ export class String {
var padLen = padString.length;
if (targetLength < length || !padLen) return this;
var len = targetLength - length;
var out = allocate(targetLength);
var out = allocateUnsafe(targetLength);
if (length) copyUnsafe(out, 0, this, 0, length);
if (len > padLen) {
let count = (len - 1) / padLen;
@ -396,10 +392,10 @@ export class String {
throw new RangeError("Invalid count value");
}
if (count === 0 || !length) return EMPTY;
if (count === 0 || !length) return changetype<String>("");
if (count === 1) return this;
var result = allocate(length * count);
var result = allocateUnsafe(length * count);
repeatUnsafe(result, 0, this, count);
return result;
}