Implement optional type parameters (#360)

* Add a NATIVE<T> macro type to simplify use of a native WebAssembly type
* Add default type parameters for internal helpers for explicit loads and stores
* Unify loadUnsafe/loadUnsafeWithOffset etc. into one
* Renamed loadUnsafe etc. into just LOAD, like a macro
* Implement parsing of index signatures, but ignore them, for properly linting code
* Refactor TypedArray<T> to use macros
This commit is contained in:
Daniel Wirtz
2018-12-07 14:33:32 +01:00
committed by GitHub
parent d7f4874650
commit ebae7cbd73
39 changed files with 4698 additions and 4128 deletions

View File

@ -1,6 +1,6 @@
import {
loadUnsafeWithOffset,
storeUnsafeWithOffset
LOAD,
STORE
} from "./arraybuffer";
import {
@ -52,15 +52,15 @@ export function insertionSort<T>(
comparator: (a: T, b: T) => i32
): void {
for (let i = 0; i < length; i++) {
let a = loadUnsafeWithOffset<T,T>(buffer, i, byteOffset); // a = arr[i]
let a = LOAD<T>(buffer, i, byteOffset); // a = arr[i]
let j = i - 1;
while (j >= 0) {
let b = loadUnsafeWithOffset<T,T>(buffer, j, byteOffset); // b = arr[j]
let b = LOAD<T>(buffer, j, byteOffset); // b = arr[j]
if (comparator(a, b) < 0) {
storeUnsafeWithOffset<T,T>(buffer, j-- + 1, b, byteOffset); // arr[j + 1] = b
STORE<T>(buffer, j-- + 1, b, byteOffset); // arr[j + 1] = b
} else break;
}
storeUnsafeWithOffset<T,T>(buffer, j + 1, a, byteOffset); // arr[j + 1] = a
STORE<T>(buffer, j + 1, a, byteOffset); // arr[j + 1] = a
}
}
@ -84,37 +84,37 @@ export function weakHeapSort<T>(
while ((j & 1) == (load<u32>(bitset + (j >> 6 << shift32)) >> (j >> 1 & 31) & 1)) j >>= 1;
let p = j >> 1;
let a = loadUnsafeWithOffset<T,T>(buffer, p, byteOffset); // a = arr[p]
let b = loadUnsafeWithOffset<T,T>(buffer, i, byteOffset); // b = arr[i]
let a = LOAD<T>(buffer, p, byteOffset); // a = arr[p]
let b = LOAD<T>(buffer, i, byteOffset); // b = arr[i]
if (comparator(a, b) < 0) {
store<u32>(
bitset + (i >> 5 << shift32),
load<u32>(bitset + (i >> 5 << shift32)) ^ (1 << (i & 31))
);
storeUnsafeWithOffset<T,T>(buffer, i, a, byteOffset); // arr[i] = a
storeUnsafeWithOffset<T,T>(buffer, p, b, byteOffset); // arr[p] = b
STORE<T>(buffer, i, a, byteOffset); // arr[i] = a
STORE<T>(buffer, p, b, byteOffset); // arr[p] = b
}
}
for (let i = length - 1; i >= 2; i--) {
let a = loadUnsafeWithOffset<T,T>(buffer, 0, byteOffset);
storeUnsafeWithOffset<T,T>(buffer, 0, loadUnsafeWithOffset<T,T>(buffer, i, byteOffset), byteOffset);
storeUnsafeWithOffset<T,T>(buffer, i, a, byteOffset);
let a = LOAD<T>(buffer, 0, byteOffset);
STORE<T>(buffer, 0, LOAD<T>(buffer, i, byteOffset), byteOffset);
STORE<T>(buffer, i, a, byteOffset);
let x = 1, y: i32;
while ((y = (x << 1) + ((load<u32>(bitset + (x >> 5 << shift32)) >> (x & 31)) & 1)) < i) x = y;
while (x > 0) {
a = loadUnsafeWithOffset<T,T>(buffer, 0, byteOffset); // a = arr[0]
let b = loadUnsafeWithOffset<T,T>(buffer, x, byteOffset); // b = arr[x]
a = LOAD<T>(buffer, 0, byteOffset); // a = arr[0]
let b = LOAD<T>(buffer, x, byteOffset); // b = arr[x]
if (comparator(a, b) < 0) {
store<u32>(
bitset + (x >> 5 << shift32),
load<u32>(bitset + (x >> 5 << shift32)) ^ (1 << (x & 31))
);
storeUnsafeWithOffset<T,T>(buffer, x, a, byteOffset); // arr[x] = a
storeUnsafeWithOffset<T,T>(buffer, 0, b, byteOffset); // arr[0] = b
STORE<T>(buffer, x, a, byteOffset); // arr[x] = a
STORE<T>(buffer, 0, b, byteOffset); // arr[0] = b
}
x >>= 1;
}
@ -122,7 +122,7 @@ export function weakHeapSort<T>(
memory.free(bitset);
var t = loadUnsafeWithOffset<T,T>(buffer, 1, byteOffset); // t = arr[1]
storeUnsafeWithOffset<T,T>(buffer, 1, loadUnsafeWithOffset<T,T>(buffer, 0, byteOffset), byteOffset);
storeUnsafeWithOffset<T,T>(buffer, 0, t, byteOffset); // arr[0] = t
var t = LOAD<T>(buffer, 1, byteOffset); // t = arr[1]
STORE<T>(buffer, 1, LOAD<T>(buffer, 0, byteOffset), byteOffset);
STORE<T>(buffer, 0, t, byteOffset); // arr[0] = t
}

View File

@ -71,33 +71,13 @@ export function reallocateUnsafe(buffer: ArrayBuffer, newByteLength: i32): Array
// * `i32.load8` ^= `<i32>load<i8>(...)` that reads an i8 but returns an i32, or
// * `i64.load32_s` ^= `<i64>load<i32>(...)`) that reads a 32-bit as a 64-bit integer
//
// without having to emit an additional instruction for conversion purposes. This is useful for
// small integers only of course. When dealing with reference types like classes, both parameters
// are usually the same, even though it looks ugly.
//
// TODO: is there a better way to model this?
// without having to emit an additional instruction for conversion purposes. The second parameter
// can be omitted for references and other loads and stores that simply return the exact type.
@inline export function loadUnsafe<T,TOut>(buffer: ArrayBuffer, index: i32): TOut {
return <TOut>load<T>(changetype<usize>(buffer) + (<usize>index << alignof<T>()), HEADER_SIZE);
@inline export function LOAD<T,TOut = T>(buffer: ArrayBuffer, index: i32, byteOffset: i32 = 0): TOut {
return <TOut>load<T>(changetype<usize>(buffer) + (<usize>index << alignof<T>()) + <usize>byteOffset, HEADER_SIZE);
}
@inline export function storeUnsafe<T,TIn>(buffer: ArrayBuffer, index: i32, value: TIn): void {
store<T>(changetype<usize>(buffer) + (<usize>index << alignof<T>()), value, HEADER_SIZE);
}
@inline export function loadUnsafeWithOffset<T,TOut>(
buffer: ArrayBuffer,
index: i32,
byteOffset: i32
): TOut {
return <TOut>load<T>(changetype<usize>(buffer) + <usize>byteOffset + (<usize>index << alignof<T>()), HEADER_SIZE);
}
@inline export function storeUnsafeWithOffset<T,TIn>(
buffer: ArrayBuffer,
index: i32,
value: TIn,
byteOffset: i32
): void {
store<T>(changetype<usize>(buffer) + <usize>byteOffset + (<usize>index << alignof<T>()), value, HEADER_SIZE);
@inline export function STORE<T,TIn = T>(buffer: ArrayBuffer, index: i32, value: TIn, byteOffset: i32 = 0): void {
store<T>(changetype<usize>(buffer) + (<usize>index << alignof<T>()) + <usize>byteOffset, value, HEADER_SIZE);
}

View File

@ -7,7 +7,7 @@ import {
} from "./string";
import {
loadUnsafe
LOAD
} from "./arraybuffer";
export const MAX_DOUBLE_LENGTH = 28;
@ -124,7 +124,7 @@ export function decimalCount32(value: u32): u32 {
let t = l * 1233 >>> 12; // log10
let lutbuf = <ArrayBuffer>POWERS10().buffer_;
let power = loadUnsafe<u32,u32>(lutbuf, t);
let power = LOAD<u32>(lutbuf, t);
t -= <u32>(value < power);
return t + 1;
} else {
@ -154,7 +154,7 @@ export function decimalCount64(value: u64): u32 {
let t = l * 1233 >>> 12; // log10
let lutbuf = <ArrayBuffer>POWERS10().buffer_;
let power = loadUnsafe<u32,u64>(lutbuf, t - 10);
let power = LOAD<u32,u64>(lutbuf, t - 10);
t -= <u32>(value < 10000000000 * power);
return t + 1;
} else {
@ -188,8 +188,8 @@ function utoa32_lut(buffer: usize, num: u32, offset: usize): void {
let d1 = r / 100;
let d2 = r % 100;
let digits1 = loadUnsafe<u32,u64>(lutbuf, d1);
let digits2 = loadUnsafe<u32,u64>(lutbuf, d2);
let digits1 = LOAD<u32,u64>(lutbuf, d1);
let digits2 = LOAD<u32,u64>(lutbuf, d2);
offset -= 4;
store<u64>(buffer + (offset << 1), digits1 | (digits2 << 32), STRING_HEADER_SIZE);
@ -200,13 +200,13 @@ function utoa32_lut(buffer: usize, num: u32, offset: usize): void {
let d1 = num % 100;
num = t;
offset -= 2;
let digits = loadUnsafe<u32,u32>(lutbuf, d1);
let digits = LOAD<u32>(lutbuf, d1);
store<u32>(buffer + (offset << 1), digits, STRING_HEADER_SIZE);
}
if (num >= 10) {
offset -= 2;
let digits = loadUnsafe<u32,u32>(lutbuf, num);
let digits = LOAD<u32>(lutbuf, num);
store<u32>(buffer + (offset << 1), digits, STRING_HEADER_SIZE);
} else {
offset -= 1;
@ -231,14 +231,14 @@ function utoa64_lut(buffer: usize, num: u64, offset: usize): void {
let c1 = c / 100;
let c2 = c % 100;
let digits1 = loadUnsafe<u32,u64>(lutbuf, c1);
let digits2 = loadUnsafe<u32,u64>(lutbuf, c2);
let digits1 = LOAD<u32,u64>(lutbuf, c1);
let digits2 = LOAD<u32,u64>(lutbuf, c2);
offset -= 4;
store<u64>(buffer + (offset << 1), digits1 | (digits2 << 32), STRING_HEADER_SIZE);
digits1 = loadUnsafe<u32,u64>(lutbuf, b1);
digits2 = loadUnsafe<u32,u64>(lutbuf, b2);
digits1 = LOAD<u32,u64>(lutbuf, b1);
digits2 = LOAD<u32,u64>(lutbuf, b2);
offset -= 4;
store<u64>(buffer + (offset << 1), digits1 | (digits2 << 32), STRING_HEADER_SIZE);
@ -438,8 +438,8 @@ function getCachedPower(minExp: i32): void {
_K = 348 - (index << 3); // decimal exponent no need lookup table
var frcPowers = <ArrayBuffer>FRC_POWERS().buffer_;
var expPowers = <ArrayBuffer>EXP_POWERS().buffer_;
_frc_pow = loadUnsafe<u64,u64>(frcPowers, index);
_exp_pow = loadUnsafe<i16,i32>(expPowers, index);
_frc_pow = LOAD<u64>(frcPowers, index);
_exp_pow = LOAD<i16,i32>(expPowers, index);
}
@inline
@ -513,7 +513,7 @@ function genDigits(buffer: usize, w_frc: u64, w_exp: i32, mp_frc: u64, mp_exp: i
let tmp = ((<u64>p1) << one_exp) + p2;
if (tmp <= delta) {
_K += kappa;
grisuRound(buffer, len, delta, tmp, loadUnsafe<u32,u64>(powers10, kappa) << one_exp, wp_w_frc);
grisuRound(buffer, len, delta, tmp, LOAD<u32,u64>(powers10, kappa) << one_exp, wp_w_frc);
return len;
}
}
@ -529,7 +529,7 @@ function genDigits(buffer: usize, w_frc: u64, w_exp: i32, mp_frc: u64, mp_exp: i
--kappa;
if (p2 < delta) {
_K += kappa;
wp_w_frc *= loadUnsafe<u32,u64>(powers10, -kappa);
wp_w_frc *= LOAD<u32,u64>(powers10, -kappa);
grisuRound(buffer, len, delta, p2, one_frc, wp_w_frc);
return len;
}

View File

@ -2,21 +2,18 @@ import {
HEADER_SIZE as AB_HEADER_SIZE,
MAX_BLENGTH as AB_MAX_BLENGTH,
allocateUnsafe,
loadUnsafeWithOffset,
storeUnsafeWithOffset
LOAD,
STORE
} from "./arraybuffer";
import {
insertionSort,
weakHeapSort,
defaultComparator
weakHeapSort
} from "./array";
// The internal TypedArray class uses two type parameters for the same reason as `loadUnsafe` and
// `storeUnsafe` in 'internal/arraybuffer.ts'. See the documentation there for details.
/** Typed array base class. Not a global object. */
export abstract class TypedArray<T,TNative> {
export abstract class TypedArray<T> {
[key: number]: T; // compatibility only
readonly buffer: ArrayBuffer;
readonly byteOffset: i32;
@ -41,116 +38,142 @@ export abstract class TypedArray<T,TNative> {
@operator("[]")
protected __get(index: i32): T {
if (<u32>index >= <u32>(this.byteLength >>> alignof<T>())) throw new Error("Index out of bounds");
return loadUnsafeWithOffset<T,T>(this.buffer, index, this.byteOffset);
return LOAD<T>(this.buffer, index, this.byteOffset);
}
@inline @operator("{}")
protected __unchecked_get(index: i32): T {
return loadUnsafeWithOffset<T,T>(this.buffer, index, this.byteOffset);
return LOAD<T>(this.buffer, index, this.byteOffset);
}
@operator("[]=")
protected __set(index: i32, value: TNative): void {
protected __set(index: i32, value: NATIVE<T>): void {
if (<u32>index >= <u32>(this.byteLength >>> alignof<T>())) throw new Error("Index out of bounds");
storeUnsafeWithOffset<T,TNative>(this.buffer, index, value, this.byteOffset);
STORE<T,NATIVE<T>>(this.buffer, index, value, this.byteOffset);
}
@inline @operator("{}=")
protected __unchecked_set(index: i32, value: TNative): void {
storeUnsafeWithOffset<T,TNative>(this.buffer, index, value, this.byteOffset);
protected __unchecked_set(index: i32, value: NATIVE<T>): void {
STORE<T,NATIVE<T>>(this.buffer, index, value, this.byteOffset);
}
// copyWithin(target: i32, start: i32, end: i32 = this.length): this
}
fill(value: TNative, start: i32 = 0, end: i32 = i32.MAX_VALUE): this {
var buffer = this.buffer;
var byteOffset = this.byteOffset;
var len = this.length;
start = start < 0 ? max(len + start, 0) : min(start, len);
end = end < 0 ? max(len + end, 0) : min(end, len);
if (sizeof<T>() == 1) {
if (start < end) {
memory.fill(
changetype<usize>(buffer) + start + byteOffset + AB_HEADER_SIZE,
<u8>value,
<usize>(end - start)
);
}
} else {
for (; start < end; ++start) {
storeUnsafeWithOffset<T,TNative>(buffer, start, value, byteOffset);
}
}
return this;
}
@inline
subarray(begin: i32 = 0, end: i32 = i32.MAX_VALUE): TypedArray<T,TNative> {
var length = this.length;
if (begin < 0) begin = max(length + begin, 0);
else begin = min(begin, length);
if (end < 0) end = max(length + end, begin);
else end = max(min(end, length), begin);
var slice = memory.allocate(offsetof<this>());
store<usize>(slice, this.buffer, offsetof<this>("buffer"));
store<i32>(slice, this.byteOffset + (begin << alignof<T>()), offsetof<this>("byteOffset"));
store<i32>(slice, (end - begin) << alignof<T>(), offsetof<this>("byteLength"));
return changetype<this>(slice);
}
sort(comparator: (a: T, b: T) => i32 = defaultComparator<T>()): this {
var byteOffset = this.byteOffset;
var length = this.length;
if (length <= 1) return this;
var buffer = this.buffer;
if (length == 2) {
let a = loadUnsafeWithOffset<T,T>(buffer, 1, byteOffset);
let b = loadUnsafeWithOffset<T,T>(buffer, 0, byteOffset);
if (comparator(a, b) < 0) {
storeUnsafeWithOffset<T,T>(buffer, 1, b, byteOffset);
storeUnsafeWithOffset<T,T>(buffer, 0, a, byteOffset);
}
return this;
}
if (isReference<T>()) {
// TODO replace this to faster stable sort (TimSort) when it implemented
insertionSort<T>(buffer, byteOffset, length, comparator);
return this;
} else {
if (length < 256) {
insertionSort<T>(buffer, byteOffset, length, comparator);
} else {
weakHeapSort<T>(buffer, byteOffset, length, comparator);
}
return this;
}
}
/**
* TypedArray reduce implementation. This is a method that will be called from the parent,
* passing types down from the child class using the typed parameters TypedArrayType and
* ReturnType respectively. This implementation requires an initial value, and the direction.
* When direction is true, reduce will reduce from the right side.
*/
@inline
protected reduce_internal<TypedArrayType, ReturnType>(
callbackfn: (accumulator: ReturnType, value: T, index: i32, array: TypedArrayType) => ReturnType,
array: TypedArrayType,
initialValue: ReturnType,
direction: bool = false,
): ReturnType {
var index: i32 = direction ? this.length - 1 : 0;
var length: i32 = direction ? -1 : this.length;
while (index != length) {
initialValue = callbackfn(
initialValue,
this.__unchecked_get(index),
index,
array,
@inline
export function FILL<TArray extends TypedArray<T>, T>(
array: TArray,
value: NATIVE<T>,
start: i32,
end: i32
): TArray {
var buffer = array.buffer;
var byteOffset = array.byteOffset;
var len = array.length;
start = start < 0 ? max(len + start, 0) : min(start, len);
end = end < 0 ? max(len + end, 0) : min(end, len);
if (sizeof<T>() == 1) {
if (start < end) {
memory.fill(
changetype<usize>(buffer) + start + byteOffset + AB_HEADER_SIZE,
<u8>value,
<usize>(end - start)
);
index = direction ? index - 1 : index + 1;
}
return initialValue;
} else {
for (; start < end; ++start) {
STORE<T,NATIVE<T>>(buffer, start, value, byteOffset);
}
}
return array;
}
@inline
export function SORT<TArray extends TypedArray<T>, T>(
array: TArray,
comparator: (a: T, b: T) => i32
): TArray {
var byteOffset = array.byteOffset;
var length = array.length;
if (length <= 1) return array;
var buffer = array.buffer;
if (length == 2) {
let a = LOAD<T>(buffer, 1, byteOffset);
let b = LOAD<T>(buffer, 0, byteOffset);
if (comparator(a, b) < 0) {
STORE<T>(buffer, 1, b, byteOffset);
STORE<T>(buffer, 0, a, byteOffset);
}
return array;
}
if (isReference<T>()) {
// TODO replace this to faster stable sort (TimSort) when it implemented
insertionSort<T>(buffer, byteOffset, length, comparator);
return array;
} else {
if (length < 256) {
insertionSort<T>(buffer, byteOffset, length, comparator);
} else {
weakHeapSort<T>(buffer, byteOffset, length, comparator);
}
return array;
}
}
@inline
export function SUBARRAY<TArray extends TypedArray<T>, T>(
array: TArray,
begin: i32,
end: i32
): TArray {
var length = <i32>array.length;
if (begin < 0) begin = max(length + begin, 0);
else begin = min(begin, length);
if (end < 0) end = max(length + end, begin);
else end = max(min(end, length), begin);
var slice = memory.allocate(offsetof<TArray>());
store<usize>(slice, array.buffer, offsetof<TArray>("buffer"));
store<i32>(slice, <i32>array.byteOffset + (begin << alignof<T>()), offsetof<TArray>("byteOffset"));
store<i32>(slice, (end - begin) << alignof<T>(), offsetof<TArray>("byteLength"));
return changetype<TArray>(slice);
}
@inline
export function REDUCE<TArray extends TypedArray<T>, T, TRet>(
array: TArray,
callbackfn: (accumulator: TRet, value: T, index: i32, array: TArray) => TRet,
initialValue: TRet
): TRet {
var index = 0;
var length = <i32>array.length;
while (index != length) {
initialValue = callbackfn(
initialValue,
unchecked(array[index]),
index,
array,
);
++index;
}
return initialValue;
}
@inline
export function REDUCE_RIGHT<TArray extends TypedArray<T>, T, TRet>(
array: TArray,
callbackfn: (accumulator: TRet, value: T, index: i32, array: TArray) => TRet,
initialValue: TRet
): TRet {
var index = <i32>array.length - 1;
var length = -1;
while (index != length) {
initialValue = callbackfn(
initialValue,
unchecked(array[index]),
index,
array,
);
--index;
}
return initialValue;
}