Skip to content

Commit b479950

Browse files
MaxGraeydcodeIO
authored andcommitted
Add fast methods for reading typed arrays to the loader (AssemblyScript#794)
1 parent 2cfda53 commit b479950

File tree

5 files changed

+120
-16
lines changed

5 files changed

+120
-16
lines changed

lib/loader/index.d.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,30 @@ interface ASUtil {
5555
__getArray(ref: number): number[];
5656
/** Gets a view on the values of an array in the module's memory. */
5757
__getArrayView(ref: number): TypedArray;
58+
/** Reads (copies) the values of Uint8Array from the module's memory. */
59+
__getUint8Array(ref: number): Uint8Array;
60+
/** Reads (copies) the values of Int8Array from the module's memory. */
61+
__getInt8Array(ref: number): Int8Array;
62+
/** Reads (copies) the values of Uint16Array from the module's memory. */
63+
__getUint16Array(ref: number): Uint16Array;
64+
/** Reads (copies) the values of Int16Array from the module's memory. */
65+
__getInt16Array(ref: number): Int16Array;
66+
/** Reads (copies) the values of Uint32Array from the module's memory. */
67+
__getUint32Array(ref: number): Uint32Array;
68+
/** Reads (copies) the values of Int32Array from the module's memory. */
69+
__getInt32Array(ref: number): Int32Array;
70+
/** Reads (copies) the values of Float32Array from the module's memory. */
71+
__getFloat32Array(ref: number): Float32Array;
72+
/** Reads (copies) the values of Float64Array from the module's memory. */
73+
__getFloat64Array(ref: number): Float64Array;
5874
/** Retains a reference externally, making sure that it doesn't become collected prematurely. Returns the reference. */
5975
__retain(ref: number): number;
6076
/** Releases a previously retained reference to an object, allowing the runtime to collect it once its reference count reaches zero. */
6177
__release(ref: number): void;
6278
/** Allocates an instance of the class represented by the specified id. */
6379
__alloc(size: number, id: number): number;
6480
/** Tests whether an object is an instance of the class represented by the specified base id. */
65-
__instanceof(ref: number, baseId: number): boolean;
81+
__instanceof(ref: number, baseId: number): boolean;
6682
/** Forces a cycle collection. Only relevant if objects potentially forming reference cycles are used. */
6783
__collect(): void;
6884
}

lib/loader/index.js

+53-15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const KEY_SIGNED = 1 << 19;
2424
const KEY_FLOAT = 1 << 20;
2525
const KEY_NULLABLE = 1 << 21;
2626
const KEY_MANAGED = 1 << 22;
27+
const KEY_ALIGN_OFFSET = 14;
28+
const VAL_ALIGN_OFFSET = 5;
2729

2830
// Array(BufferView) layout
2931
const ARRAYBUFFERVIEW_BUFFER_OFFSET = 0;
@@ -109,6 +111,7 @@ function postInstantiate(baseModule, instance) {
109111
F64 = new Float64Array(buffer);
110112
}
111113
}
114+
112115
checkMem();
113116

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

128-
/** Gets the runtime alignment of a collection's values or keys. */
129-
function getAlign(which, info) {
130-
return 31 - Math.clz32((info / which) & 31); // -1 if none
131+
/** Gets the runtime alignment of a collection's values. */
132+
function getAlignValue(info) {
133+
return 31 - Math.clz32((info >>> VAL_ALIGN_OFFSET) & 31); // -1 if none
134+
}
135+
136+
/** Gets the runtime alignment of a collection's keys. */
137+
function getAlignKey(info) {
138+
return 31 - Math.clz32((info >>> KEY_ALIGN_OFFSET) & 31); // -1 if none
139+
}
140+
141+
function getTypedArray(Type, shift, arr) {
142+
var buffer = memory.buffer;
143+
var u32 = new Uint32Array(buffer);
144+
var buf = u32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2];
145+
var length = u32[buf + SIZE_OFFSET >>> 2];
146+
return new Type(buffer).slice(buf >>> shift, buf + length >>> shift);
131147
}
132148

133149
/** Allocates a new string in the module's memory and returns its retained pointer. */
134150
function __allocString(str) {
135-
const length = str.length;
136-
const ref = alloc(length << 1, STRING_ID);
137-
checkMem();
138-
for (let i = 0, j = ref >>> 1; i < length; ++i) U16[j + i] = str.charCodeAt(i);
151+
var length = str.length;
152+
var ref = alloc(length << 1, STRING_ID);
153+
var u16 = new Uint16Array(memory.buffer);
154+
for (var i = 0, p = ref >>> 1; i < length; ++i) u16[p + i] = str.charCodeAt(i);
139155
return ref;
140156
}
141157

142158
baseModule.__allocString = __allocString;
143159

144160
/** Reads a string from the module's memory by its pointer. */
145161
function __getString(ref) {
146-
checkMem();
147-
const id = U32[ref + ID_OFFSET >>> 2];
162+
var buf = memory.buffer;
163+
var u16 = new Uint16Array(buf);
164+
var u32 = new Uint32Array(buf);
165+
var id = u32[ref + ID_OFFSET >>> 2];
148166
if (id !== STRING_ID) throw Error("not a string: " + ref);
149-
return getStringImpl(U32, U16, ref);
167+
return getStringImpl(u32, u16, ref);
150168
}
151169

152170
baseModule.__getString = __getString;
@@ -173,7 +191,7 @@ function postInstantiate(baseModule, instance) {
173191
function __allocArray(id, values) {
174192
const info = getInfo(id);
175193
if (!(info & (ARRAYBUFFERVIEW | ARRAY))) throw Error("not an array: " + id + " @ " + info);
176-
const align = getAlign(VAL_ALIGN, info);
194+
const align = getAlignValue(info);
177195
const length = values.length;
178196
const buf = alloc(length << align, ARRAYBUFFER_ID);
179197
const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
@@ -183,8 +201,11 @@ function postInstantiate(baseModule, instance) {
183201
U32[arr + ARRAYBUFFERVIEW_DATALENGTH_OFFSET >>> 2] = length << align;
184202
if (info & ARRAY) U32[arr + ARRAY_LENGTH_OFFSET >>> 2] = length;
185203
const view = getView(align, info & VAL_SIGNED, info & VAL_FLOAT);
186-
for (let i = 0; i < length; ++i) view[(buf >> align) + i] = values[i];
187-
if (info & VAL_MANAGED) for (let i = 0; i < length; ++i) retain(values[i]);
204+
if (info & VAL_MANAGED) {
205+
for (let i = 0; i < length; ++i) view[(buf >>> align) + i] = retain(values[i]);
206+
} else {
207+
view.set(values, buf >>> align);
208+
}
188209
return arr;
189210
}
190211

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

215236
baseModule.__getArray = __getArray;
216237

238+
function __getArrayBuffer(buf) {
239+
var buffer = memory.buffer;
240+
var length = (new Uint32Array(buffer))[buf + SIZE_OFFSET >>> 2];
241+
return buffer.slice(buf, buf + length);
242+
}
243+
244+
baseModule.__getArrayBuffer = __getArrayBuffer;
245+
246+
baseModule.__getUint8Array = getTypedArray.bind(null, Uint8Array, 0);
247+
baseModule.__getInt8Array = getTypedArray.bind(null, Int8Array, 0);
248+
baseModule.__getUint16Array = getTypedArray.bind(null, Uint16Array, 1);
249+
baseModule.__getInt16Array = getTypedArray.bind(null, Int16Array, 1);
250+
baseModule.__getUint32Array = getTypedArray.bind(null, Uint32Array, 2);
251+
baseModule.__getInt32Array = getTypedArray.bind(null, Int32Array, 2);
252+
baseModule.__getFloat32Array = getTypedArray.bind(null, Float32Array, 2);
253+
baseModule.__getFloat64Array = getTypedArray.bind(null, Float64Array, 3);
254+
217255
/** Tests whether an object is an instance of the class represented by the specified base id. */
218256
function __instanceof(ref, baseId) {
219257
var id = U32[(ref + ID_OFFSET) >>> 2];
@@ -228,7 +266,7 @@ function postInstantiate(baseModule, instance) {
228266

229267
// Pull basic exports to baseModule so code in preInstantiate can use them
230268
baseModule.memory = baseModule.memory || memory;
231-
baseModule.table = baseModule.table || table;
269+
baseModule.table = baseModule.table || table;
232270

233271
// Demangle exports and provide the usual utility on the prototype
234272
return demangle(rawExports, Object.defineProperties(baseModule, {

lib/loader/tests/assembly/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export function dotrace(num: f64): void {
6363
trace("The answer is", 1, num);
6464
}
6565

66+
export const UINT8ARRAY_ID = idof<Uint8Array>();
67+
export const INT16ARRAY_ID = idof<Int16Array>();
68+
export const UINT16ARRAY_ID = idof<Uint16Array>();
6669
export const INT32ARRAY_ID = idof<Int32Array>();
6770
export const UINT32ARRAY_ID = idof<Uint32Array>();
6871
export const FLOAT32ARRAY_ID = idof<Float32Array>();

lib/loader/tests/build/untouched.wasm

182 Bytes
Binary file not shown.

lib/loader/tests/index.js

+47
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,57 @@ assert.strictEqual(module.__getString(module.COLOR), "red");
5555
try { module.__release(ref); assert(false); } catch (e) {};
5656
}
5757

58+
/*
59+
{
60+
let arrU8Arr = new Uint8Array([0, 1, 2]);
61+
let refU8Arr = module.__retain(module.__allocUint8Array(arrU8Arr));
62+
assert(module.__instanceof(refU8Arr, module.UINT8ARRAY_ID));
63+
assert.deepEqual(module.__getUint8Array(refU8Arr), arrU8Arr);
64+
module.__release(refU8Arr);
65+
try { module.__release(refU8Arr); assert(false); } catch (e) {};
66+
67+
let arrU16Arr = new Uint16Array([0, 0x7FFF, 0xFFFF]);
68+
let refU16Arr = module.__retain(module.__allocUint16Array(arrU16Arr));
69+
assert(module.__instanceof(refU16Arr, module.UINT16ARRAY_ID));
70+
assert.deepEqual(module.__getUint16Array(refU16Arr), arrU16Arr);
71+
module.__release(refU16Arr);
72+
try { module.__release(refU16Arr); assert(false); } catch (e) {};
73+
74+
let arrI16Arr = new Int16Array([0, -1, -2]);
75+
let refI16Arr = module.__retain(module.__allocInt16Array(arrI16Arr));
76+
assert(module.__instanceof(refI16Arr, module.INT16ARRAY_ID));
77+
assert.deepEqual(module.__getInt16Array(refI16Arr), arrI16Arr);
78+
module.__release(refI16Arr);
79+
try { module.__release(refI16Arr); assert(false); } catch (e) {};
80+
}
81+
*/
82+
83+
// should be able to distinguish between signed and unsigned
84+
{
85+
let arr = new Uint8Array([0, 255, 127]);
86+
let ref = module.__retain(module.__allocArray(module.UINT8ARRAY_ID, arr));
87+
assert(module.__instanceof(ref, module.UINT8ARRAY_ID));
88+
assert.deepEqual(module.__getUint8Array(ref), arr);
89+
module.__release(ref);
90+
try { module.__release(ref); assert(false); } catch (e) {};
91+
}
92+
93+
// should be able to distinguish between signed and unsigned
94+
{
95+
let arr = new Int16Array([0, 0xFFFF, -0x00FF]);
96+
let ref = module.__retain(module.__allocArray(module.INT16ARRAY_ID, arr));
97+
assert(module.__instanceof(ref, module.INT16ARRAY_ID));
98+
assert.deepEqual(module.__getInt16Array(ref), arr);
99+
module.__release(ref);
100+
try { module.__release(ref); assert(false); } catch (e) {};
101+
}
102+
58103
// should be able to distinguish between signed and unsigned
59104
{
60105
let arr = [1, -1 >>> 0, 0x80000000];
61106
let ref = module.__retain(module.__allocArray(module.UINT32ARRAY_ID, arr));
62107
assert(module.__instanceof(ref, module.UINT32ARRAY_ID));
108+
assert.deepEqual(module.__getUint32Array(ref), new Uint32Array(arr));
63109
assert.deepEqual(module.__getArray(ref), arr);
64110
module.__release(ref);
65111
try { module.__release(ref); assert(false); } catch (e) {};
@@ -70,6 +116,7 @@ assert.strictEqual(module.__getString(module.COLOR), "red");
70116
let arr = [0.0, 1.5, 2.5];
71117
let ref = module.__retain(module.__allocArray(module.FLOAT32ARRAY_ID, arr));
72118
assert(module.__instanceof(ref, module.FLOAT32ARRAY_ID));
119+
assert.deepEqual(module.__getFloat32Array(ref), new Float32Array(arr));
73120
assert.deepEqual(module.__getArray(ref), arr);
74121
module.__release(ref);
75122
try { module.__release(ref); assert(false); } catch (e) {};

0 commit comments

Comments
 (0)