Skip to content

Commit

Permalink
Add fast methods for reading typed arrays to the loader (#794)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxGraey authored and dcodeIO committed Sep 2, 2019
1 parent 2cfda53 commit b479950
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 16 deletions.
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
}

/** 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

0 comments on commit b479950

Please sign in to comment.