From b4799500f1a1d8fc08e86abdc7535cc1abf78884 Mon Sep 17 00:00:00 2001 From: Max Graey Date: Mon, 2 Sep 2019 11:39:21 +0300 Subject: [PATCH] Add fast methods for reading typed arrays to the loader (#794) --- lib/loader/index.d.ts | 18 ++++++- lib/loader/index.js | 68 ++++++++++++++++++++------ lib/loader/tests/assembly/index.ts | 3 ++ lib/loader/tests/build/untouched.wasm | Bin 9477 -> 9659 bytes lib/loader/tests/index.js | 47 ++++++++++++++++++ 5 files changed, 120 insertions(+), 16 deletions(-) diff --git a/lib/loader/index.d.ts b/lib/loader/index.d.ts index fa239e99db..0ce0221a0a 100644 --- a/lib/loader/index.d.ts +++ b/lib/loader/index.d.ts @@ -55,6 +55,22 @@ 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. */ @@ -62,7 +78,7 @@ interface ASUtil { /** 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; } diff --git a/lib/loader/index.js b/lib/loader/index.js index 5864874886..2a759abd21 100644 --- a/lib/loader/index.js +++ b/lib/loader/index.js @@ -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; @@ -109,6 +111,7 @@ function postInstantiate(baseModule, instance) { F64 = new Float64Array(buffer); } } + checkMem(); /** Gets the runtime type info for the given id. */ @@ -125,17 +128,30 @@ 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; } @@ -143,10 +159,12 @@ function postInstantiate(baseModule, instance) { /** 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; @@ -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); @@ -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; } @@ -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] @@ -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]; @@ -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, { diff --git a/lib/loader/tests/assembly/index.ts b/lib/loader/tests/assembly/index.ts index 3b68e105aa..3dfb13ab55 100644 --- a/lib/loader/tests/assembly/index.ts +++ b/lib/loader/tests/assembly/index.ts @@ -63,6 +63,9 @@ export function dotrace(num: f64): void { trace("The answer is", 1, num); } +export const UINT8ARRAY_ID = idof(); +export const INT16ARRAY_ID = idof(); +export const UINT16ARRAY_ID = idof(); export const INT32ARRAY_ID = idof(); export const UINT32ARRAY_ID = idof(); export const FLOAT32ARRAY_ID = idof(); diff --git a/lib/loader/tests/build/untouched.wasm b/lib/loader/tests/build/untouched.wasm index fd6471c9dcec11253664018e36154597da8cc9ce..539108e521de0bf19ac1d45d851b3e2a23903ea0 100644 GIT binary patch delta 1315 zcmZWpTWDNG7@mJ-&R)*!X3s`<6SG+}XH&?=G=U_wsd&kbHL2TbR)a=uOZCC)Zh~wZ zjRon7Cx%}zL+plu)8ybq7gW%B8)w8E ze2kxn%kTppk~#PhPslIeciiLc>-`%{GGI(;FhNzvs#;F3h5v(ByeHr_oQa%=S8*^p z0&n7@(M@m^4@L7bFtCW9M|VMI(~l(qzQgV~!a4jj{zB|mZqha>&iEVfe!ti_3=p9b z&9&9lRVd369l_)@JX2dVP^*TZCT;Ol12qsVnxMuTnuS7(B0rW;+ZM@%tgEY{YK3X+ ztDE4(P-9-k{Zch7A9p2R2G1l zm+P8W*4q9PzfqOGIRL7l$fW~*g`P&;R$VpjjJGB3gic&a48Vu@K_XAQ_$o1Y(>rMp z4PFu@&86*E)IDO;R1)yFbOCiTmLpIaHY1B+)3Vg^H9^JtnB)U~%g#-#j+4o-c_!Hp z!rCr&9F*3E)zp(t@l&d;*csgI6z>NuMYEW$NLa>~GP`|>)PfpIcz7W*ly=D-q%biD z*_G8Pr&TSrg}Rg}B($Ktgd*V}>)D*`p`A(L#cX!V|58yzj3PAcf=EPSP_PNXTn4o+ zI|XN2h)UhX`q|pp&!XR@;p^s5*DfGV9qgXu5a72xv*2S-?lAvS;>p~eiK~uDJctmK zqrBA8QQmz4vbCN1lpA~dGq@&<#Npm|@m%jHtXP=tQ_^s||9(cR2x=OFTZsPZLi3LN zvDUnQxp`A--kHArs;5ncM96WuZ-DAr>DzhhIPivHmV{c&hn^dt)Lz++ifLKZpm~s{ zS@3Eo+jFSml%89#~5Z>8c`#D)Z`x&)PsAICvPQXo-v_OO_5W!m@YRr#Ol<=37rcw{B(~AqV zLR4}Qv>XrtG$>T6RDmK6fgS*d69+gUApsHx5aRa$RaF!?1aS_t4~)p6vNZC{+xceZ z`@T!-ek&fG?d)JrD`%jb#UiU3sVjH6Pn17po(6izdhsLUg9A`e)+(2cmEEr~* z&2bzNoiL7LVlzzOKjL4wjyGfqZsLD(3ZCF!p`9%=U|f$erNMY*9Vu(sTW^1fo#7GK zk7MC`un)g?y5JE0;dH|Zyy2uJH{K8U!Wn{itr&3uF5}h)gj+b>usiaIn}h|$vBow$ z+?dM`K)AZft_NmzR+Mcp)twdN{p1VHGg6C8NJ||mru?uD7nosR%7PY{Q=h{$exr`T zE_|uF^h06d7FcOP=b8XS^HP1|$Ji4swGRVSHOq17Lojc4mf2zSAME5eDgGT@32}TD zUDS%w@HZ4#_I_N(nPwV1p}Ght8^h1skCR6dAga6|3Ytse+K5VsbjE$LHf5VeeI5m+ z@TB_zCSpF`aTg29YbsU*E(^FdelSeAiVD2zeTu)%%V6H~?3bc<(7wdm-WN9gC=sN! z(}|LV1NiF0;RZ_t9T*McQ1X+wMG5v0CQ_n?6G`pb+S?*kt|mJ+*a{J9oQe)n=#_<_ zC!~pcr}7R_X{yln89}IlCmu)AeP+cc0rH21>fhQP;?b|)X#VV(=);$c{=X6twh+tZl zHPGw?F~G~d46aw-;ZvQrf-LZp3+?S%knSNONJ|Q%R(+)e12xWplijj$9P&>!)$LWW yyuRl)H?yU6FWHHc5X5c$M{7q$$;q(@WB7aKXXz?+ss`DwIA0UNk@gf~d+s%%Mfwi_ diff --git a/lib/loader/tests/index.js b/lib/loader/tests/index.js index c45ab474c0..fddf10d8d4 100644 --- a/lib/loader/tests/index.js +++ b/lib/loader/tests/index.js @@ -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) {}; @@ -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) {};