mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-12 06:21:29 +00:00
Initial GC integration (#196)
This commit is contained in:
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
5
std/assembly/diagnostics.ts
Normal file
5
std/assembly/diagnostics.ts
Normal 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;
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
6
std/assembly/index.d.ts
vendored
6
std/assembly/index.d.ts
vendored
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
254
std/assembly/internal/memory.ts
Normal file
254
std/assembly/internal/memory.ts
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user