Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[loader] Add fast methods for reading typed arrays #794

Merged
merged 20 commits into from
Sep 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion lib/loader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,30 @@ interface ASUtil {
__getArray(ref: number): number[];
/** Gets a view on the values of an array in the module's memory. */
__getArrayView(ref: number): TypedArray;
/** Reads (copies) the values of Uint8Array from the module's memory. */
__getUint8Array(ref: number): Uint8Array;
/** Reads (copies) the values of Int8Array from the module's memory. */
__getInt8Array(ref: number): Int8Array;
/** Reads (copies) the values of Uint16Array from the module's memory. */
__getUint16Array(ref: number): Uint16Array;
/** Reads (copies) the values of Int16Array from the module's memory. */
__getInt16Array(ref: number): Int16Array;
/** Reads (copies) the values of Uint32Array from the module's memory. */
__getUint32Array(ref: number): Uint32Array;
/** Reads (copies) the values of Int32Array from the module's memory. */
__getInt32Array(ref: number): Int32Array;
/** Reads (copies) the values of Float32Array from the module's memory. */
__getFloat32Array(ref: number): Float32Array;
/** Reads (copies) the values of Float64Array from the module's memory. */
__getFloat64Array(ref: number): Float64Array;
/** Retains a reference externally, making sure that it doesn't become collected prematurely. Returns the reference. */
__retain(ref: number): number;
/** Releases a previously retained reference to an object, allowing the runtime to collect it once its reference count reaches zero. */
__release(ref: number): void;
/** Allocates an instance of the class represented by the specified id. */
__alloc(size: number, id: number): number;
/** Tests whether an object is an instance of the class represented by the specified base id. */
__instanceof(ref: number, baseId: number): boolean;
__instanceof(ref: number, baseId: number): boolean;
/** Forces a cycle collection. Only relevant if objects potentially forming reference cycles are used. */
__collect(): void;
}
Expand Down
68 changes: 53 additions & 15 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const KEY_SIGNED = 1 << 19;
const KEY_FLOAT = 1 << 20;
const KEY_NULLABLE = 1 << 21;
const KEY_MANAGED = 1 << 22;
const KEY_ALIGN_OFFSET = 14;
const VAL_ALIGN_OFFSET = 5;

// Array(BufferView) layout
const ARRAYBUFFERVIEW_BUFFER_OFFSET = 0;
Expand Down Expand Up @@ -109,6 +111,7 @@ function postInstantiate(baseModule, instance) {
F64 = new Float64Array(buffer);
}
}

checkMem();

/** Gets the runtime type info for the given id. */
Expand All @@ -125,28 +128,43 @@ function postInstantiate(baseModule, instance) {
return U32[(rttiBase + 4 >>> 2) + id * 2 + 1];
}

/** Gets the runtime alignment of a collection's values or keys. */
function getAlign(which, info) {
return 31 - Math.clz32((info / which) & 31); // -1 if none
/** Gets the runtime alignment of a collection's values. */
function getAlignValue(info) {
return 31 - Math.clz32((info >>> VAL_ALIGN_OFFSET) & 31); // -1 if none
MaxGraey marked this conversation as resolved.
Show resolved Hide resolved
}

/** Gets the runtime alignment of a collection's keys. */
function getAlignKey(info) {
return 31 - Math.clz32((info >>> KEY_ALIGN_OFFSET) & 31); // -1 if none
}

function getTypedArray(Type, shift, arr) {
var buffer = memory.buffer;
var u32 = new Uint32Array(buffer);
var buf = u32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2];
var length = u32[buf + SIZE_OFFSET >>> 2];
return new Type(buffer).slice(buf >>> shift, buf + length >>> shift);
}

/** Allocates a new string in the module's memory and returns its retained pointer. */
function __allocString(str) {
const length = str.length;
const ref = alloc(length << 1, STRING_ID);
checkMem();
for (let i = 0, j = ref >>> 1; i < length; ++i) U16[j + i] = str.charCodeAt(i);
var length = str.length;
var ref = alloc(length << 1, STRING_ID);
var u16 = new Uint16Array(memory.buffer);
for (var i = 0, p = ref >>> 1; i < length; ++i) u16[p + i] = str.charCodeAt(i);
return ref;
}

baseModule.__allocString = __allocString;

/** Reads a string from the module's memory by its pointer. */
function __getString(ref) {
checkMem();
const id = U32[ref + ID_OFFSET >>> 2];
var buf = memory.buffer;
var u16 = new Uint16Array(buf);
var u32 = new Uint32Array(buf);
var id = u32[ref + ID_OFFSET >>> 2];
if (id !== STRING_ID) throw Error("not a string: " + ref);
return getStringImpl(U32, U16, ref);
return getStringImpl(u32, u16, ref);
}

baseModule.__getString = __getString;
Expand All @@ -173,7 +191,7 @@ function postInstantiate(baseModule, instance) {
function __allocArray(id, values) {
const info = getInfo(id);
if (!(info & (ARRAYBUFFERVIEW | ARRAY))) throw Error("not an array: " + id + " @ " + info);
const align = getAlign(VAL_ALIGN, info);
const align = getAlignValue(info);
const length = values.length;
const buf = alloc(length << align, ARRAYBUFFER_ID);
const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
Expand All @@ -183,8 +201,11 @@ function postInstantiate(baseModule, instance) {
U32[arr + ARRAYBUFFERVIEW_DATALENGTH_OFFSET >>> 2] = length << align;
if (info & ARRAY) U32[arr + ARRAY_LENGTH_OFFSET >>> 2] = length;
const view = getView(align, info & VAL_SIGNED, info & VAL_FLOAT);
for (let i = 0; i < length; ++i) view[(buf >> align) + i] = values[i];
if (info & VAL_MANAGED) for (let i = 0; i < length; ++i) retain(values[i]);
if (info & VAL_MANAGED) {
for (let i = 0; i < length; ++i) view[(buf >>> align) + i] = retain(values[i]);
} else {
view.set(values, buf >>> align);
}
return arr;
}

Expand All @@ -196,7 +217,7 @@ function postInstantiate(baseModule, instance) {
const id = U32[arr + ID_OFFSET >>> 2];
const info = getInfo(id);
if (!(info & ARRAYBUFFERVIEW)) throw Error("not an array: " + id);
const align = getAlign(VAL_ALIGN, info);
const align = getAlignValue(info);
var buf = U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2];
const length = info & ARRAY
? U32[arr + ARRAY_LENGTH_OFFSET >>> 2]
Expand All @@ -214,6 +235,23 @@ function postInstantiate(baseModule, instance) {

baseModule.__getArray = __getArray;

function __getArrayBuffer(buf) {
var buffer = memory.buffer;
var length = (new Uint32Array(buffer))[buf + SIZE_OFFSET >>> 2];
return buffer.slice(buf, buf + length);
}

baseModule.__getArrayBuffer = __getArrayBuffer;

baseModule.__getUint8Array = getTypedArray.bind(null, Uint8Array, 0);
baseModule.__getInt8Array = getTypedArray.bind(null, Int8Array, 0);
baseModule.__getUint16Array = getTypedArray.bind(null, Uint16Array, 1);
baseModule.__getInt16Array = getTypedArray.bind(null, Int16Array, 1);
baseModule.__getUint32Array = getTypedArray.bind(null, Uint32Array, 2);
baseModule.__getInt32Array = getTypedArray.bind(null, Int32Array, 2);
baseModule.__getFloat32Array = getTypedArray.bind(null, Float32Array, 2);
baseModule.__getFloat64Array = getTypedArray.bind(null, Float64Array, 3);

/** Tests whether an object is an instance of the class represented by the specified base id. */
function __instanceof(ref, baseId) {
var id = U32[(ref + ID_OFFSET) >>> 2];
Expand All @@ -228,7 +266,7 @@ function postInstantiate(baseModule, instance) {

// Pull basic exports to baseModule so code in preInstantiate can use them
baseModule.memory = baseModule.memory || memory;
baseModule.table = baseModule.table || table;
baseModule.table = baseModule.table || table;

// Demangle exports and provide the usual utility on the prototype
return demangle(rawExports, Object.defineProperties(baseModule, {
Expand Down
3 changes: 3 additions & 0 deletions lib/loader/tests/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export function dotrace(num: f64): void {
trace("The answer is", 1, num);
}

export const UINT8ARRAY_ID = idof<Uint8Array>();
export const INT16ARRAY_ID = idof<Int16Array>();
export const UINT16ARRAY_ID = idof<Uint16Array>();
export const INT32ARRAY_ID = idof<Int32Array>();
export const UINT32ARRAY_ID = idof<Uint32Array>();
export const FLOAT32ARRAY_ID = idof<Float32Array>();
Expand Down
Binary file modified lib/loader/tests/build/untouched.wasm
Binary file not shown.
47 changes: 47 additions & 0 deletions lib/loader/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,57 @@ assert.strictEqual(module.__getString(module.COLOR), "red");
try { module.__release(ref); assert(false); } catch (e) {};
}

/*
{
let arrU8Arr = new Uint8Array([0, 1, 2]);
let refU8Arr = module.__retain(module.__allocUint8Array(arrU8Arr));
assert(module.__instanceof(refU8Arr, module.UINT8ARRAY_ID));
assert.deepEqual(module.__getUint8Array(refU8Arr), arrU8Arr);
module.__release(refU8Arr);
try { module.__release(refU8Arr); assert(false); } catch (e) {};

let arrU16Arr = new Uint16Array([0, 0x7FFF, 0xFFFF]);
let refU16Arr = module.__retain(module.__allocUint16Array(arrU16Arr));
assert(module.__instanceof(refU16Arr, module.UINT16ARRAY_ID));
assert.deepEqual(module.__getUint16Array(refU16Arr), arrU16Arr);
module.__release(refU16Arr);
try { module.__release(refU16Arr); assert(false); } catch (e) {};

let arrI16Arr = new Int16Array([0, -1, -2]);
let refI16Arr = module.__retain(module.__allocInt16Array(arrI16Arr));
assert(module.__instanceof(refI16Arr, module.INT16ARRAY_ID));
assert.deepEqual(module.__getInt16Array(refI16Arr), arrI16Arr);
module.__release(refI16Arr);
try { module.__release(refI16Arr); assert(false); } catch (e) {};
}
*/

// should be able to distinguish between signed and unsigned
{
let arr = new Uint8Array([0, 255, 127]);
let ref = module.__retain(module.__allocArray(module.UINT8ARRAY_ID, arr));
assert(module.__instanceof(ref, module.UINT8ARRAY_ID));
assert.deepEqual(module.__getUint8Array(ref), arr);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
}

// should be able to distinguish between signed and unsigned
{
let arr = new Int16Array([0, 0xFFFF, -0x00FF]);
let ref = module.__retain(module.__allocArray(module.INT16ARRAY_ID, arr));
assert(module.__instanceof(ref, module.INT16ARRAY_ID));
assert.deepEqual(module.__getInt16Array(ref), arr);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
}

// should be able to distinguish between signed and unsigned
{
let arr = [1, -1 >>> 0, 0x80000000];
let ref = module.__retain(module.__allocArray(module.UINT32ARRAY_ID, arr));
assert(module.__instanceof(ref, module.UINT32ARRAY_ID));
assert.deepEqual(module.__getUint32Array(ref), new Uint32Array(arr));
assert.deepEqual(module.__getArray(ref), arr);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
Expand All @@ -70,6 +116,7 @@ assert.strictEqual(module.__getString(module.COLOR), "red");
let arr = [0.0, 1.5, 2.5];
let ref = module.__retain(module.__allocArray(module.FLOAT32ARRAY_ID, arr));
assert(module.__instanceof(ref, module.FLOAT32ARRAY_ID));
assert.deepEqual(module.__getFloat32Array(ref), new Float32Array(arr));
assert.deepEqual(module.__getArray(ref), arr);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
Expand Down