Skip to content

Commit

Permalink
Add initial newArray and getArray helpers to loader
Browse files Browse the repository at this point in the history
Essentially creates an unmanaged typed array in memory that one can work with and free again respectively obtain from the AS side. No support for GC or generic arrays yet, and is likely to change substentially once WASM GC becomes a thing.
  • Loading branch information
dcodeIO committed Sep 18, 2018
1 parent 16d1a83 commit 9c770d8
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 9 deletions.
2 changes: 1 addition & 1 deletion dist/asc.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/asc.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/assemblyscript.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/assemblyscript.js.map

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions lib/loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Instances are automatically populated with useful utility:
A 64-bit float view on the memory.

* **newString**(str: `string`): `number`<br />
Allocates a new string in the module's memory and returns its pointer. Requires `allocate_memory` to be exported from your module's entry file, i.e.:
Allocates a new string in the module's memory and returns its pointer. Requires `memory.allocate` to be exported from your module's entry file, i.e.:

```js
import "allocator/tlsf";
Expand All @@ -71,6 +71,18 @@ Instances are automatically populated with useful utility:
* **getString**(ptr: `number`): `string`<br />
Gets a string from the module's memory by its pointer.
* **newArray**(view: `TypedArray`, length?: `number`, unsafe?: `boolean`): `number`<br />
Copies a typed array into the module's memory and returns its pointer.

* **newArray**(ctor: `TypedArrayConstructor`, length: `number`): `number`<br />
Creates a typed array in the module's memory and returns its pointer.
* **getArray**(ctor: `TypedArrayConstructor`, ptr: `number`): `TypedArray`<br />
Gets a view on a typed array in the module's memory by its pointer.

* **freeArray**(ptr: `number`): `void`<br />
Frees a typed array in the module's memory. Must not be accessed anymore afterwards.
<sup>1</sup> This feature has not yet landed in any VM as of this writing.
Examples
Expand Down Expand Up @@ -125,7 +137,7 @@ myModule.F64[ptrToFloat64 >>> 3] = newValue;
var str = "Hello world!";
var ptr = module.newString(str);
// Disposing a string that is no longer needed (requires free_memory to be exported)
// Disposing a string that is no longer needed (requires memory.free to be exported)
module.memory.free(ptr);
// Obtaining a string, i.e. as returned by an export
Expand Down
28 changes: 28 additions & 0 deletions lib/loader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ interface ImportsObject {
}
}

type TypedArray
= Int8Array
| Uint8Array
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array;

type TypedArrayConstructor
= Int8ArrayConstructor
| Uint8ArrayConstructor
| Int16ArrayConstructor
| Uint16ArrayConstructor
| Int32ArrayConstructor
| Uint32ArrayConstructor
| Float32ArrayConstructor
| Float32ArrayConstructor;

/** Utility mixed in by the loader. */
interface ASUtil {
/** An 8-bit signed integer view on the memory. */
Expand All @@ -36,6 +56,14 @@ interface ASUtil {
newString(str: string): number;
/** Gets a string from the module's memory by its pointer. */
getString(ptr: number): string;
/** Copies a typed array into the module's memory and returns its pointer. */
newArray(view: TypedArray, length?: number): number;
/** Creates a typed array in the module's memory and returns its pointer. */
newArray(ctor: TypedArrayConstructor, length: number, unsafe?: boolean): number;
/** Gets a view on a typed array in the module's memory by its pointer. */
getArray(ctor: TypedArrayConstructor, ptr: number): TypedArray;
/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
freeArray(ptr: number): void;
}

/** Instantiates an AssemblyScript module using the specified imports. */
Expand Down
66 changes: 64 additions & 2 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ function instantiate(module, imports) {
// Instantiate the module and obtain its (flat) exports
var instance = new WebAssembly.Instance(module, imports);
var exports = instance.exports;
var memory_allocate = exports["memory.allocate"];
var memory_fill = exports["memory.fill"];
var memory_free = exports["memory.free"];

// Provide views for all sorts of basic values
var mem, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
Expand Down Expand Up @@ -45,7 +48,7 @@ function instantiate(module, imports) {
/** Allocates a new string in the module's memory and returns its pointer. */
function newString(str) {
var dataLength = str.length;
var ptr = exports["memory.allocate"](4 + (dataLength << 1));
var ptr = memory_allocate(4 + (dataLength << 1));
var dataOffset = (4 + ptr) >>> 1;
checkMem();
U32[ptr >>> 2] = dataLength;
Expand All @@ -71,6 +74,62 @@ function instantiate(module, imports) {
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
}

function computeBufferSize(byteLength) {
const HEADER_SIZE = 8;
return 1 << (32 - Math.clz32(byteLength + HEADER_SIZE - 1));
}

/** Creates a new typed array in the module's memory and returns its pointer. */
function newArray(view, length, unsafe) {
var ctor = view.constructor;
if (ctor === Function) { // TypedArray constructor created in memory
ctor = view;
view = null;
} else { // TypedArray instance copied into memory
if (length === undefined) length = view.length;
}
var elementSize = ctor.BYTES_PER_ELEMENT;
if (!elementSize) throw Error("not a typed array");
var byteLength = elementSize * length;
var ptr = memory_allocate(12); // TypedArray header
var buf = memory_allocate(computeBufferSize(byteLength)); // ArrayBuffer
checkMem();
U32[ ptr >>> 2] = buf; // .buffer
U32[(ptr + 4) >>> 2] = 0; // .byteOffset
U32[(ptr + 8) >>> 2] = byteLength; // .byteLength
U32[ buf >>> 2] = byteLength; // .byteLength
U32[(buf + 4) >>> 2] = 0; // 0
if (view) {
new ctor(mem, buf + 8, length).set(view);
if (view.length < length && !unsafe) {
let setLength = elementSize * view.length;
memory_fill(buf + 8 + setLength, 0, byteLength - setLength);
}
} else if (!unsafe) {
memory_fill(buf + 8, 0, byteLength);
}
return ptr;
}

/** Gets a view on a typed array in the module's memory by its pointer. */
function getArray(ctor, ptr) {

This comment has been minimized.

Copy link
@dy

dy Jan 8, 2019

Interesting that this method does not work with Uint32Arrays/Float64Arrays on my side.

The code I have is:

export function createReverseOrderedArray(size: i32): Array<f64> {
  var arr = new Array<f64>(size);
  for (let i = 0; i < arr.length; i++) {
    arr[i] = arr.length - 1 - i;
  }
  return arr;
}
...
let ptr = instance.createReverseOrderedArray(16)
instance.getArray(Float64Array, ptr)

C:\projects\audio-noise\node_modules\assemblyscript\lib\loader\index.js:152
    return new ctor(buffer, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize);
           ^

RangeError: Invalid typed array length: -2
    at typedArrayConstructByArrayBuffer (<anonymous>)

Whereas the version suggested by @vdeturckheim works fine

let ptr = instance.createReverseOrderedArray(16)
let ptr0 = ptr >>> 2
let dataStart = (instance.U32[ptr0] >>> 2) + 2;
let len = instance.U32[ptr0 + 1];

instance.F64.subarray(dataStart >>> 1, (dataStart >>> 1) + len); // [15...0] array

Maybe there should be test-cases for that?
@dcodeIO

This comment has been minimized.

Copy link
@dcodeIO

dcodeIO Jan 8, 2019

Author Member

Note that new Array<f64> isn't a typed array. Try new Float64Array instead.

This comment has been minimized.

Copy link
@dy

dy Jan 8, 2019

@dcodeIO that worked, thanks. What is supposed way to workaround Arrays? Convert them to TypedArray?

var elementSize = ctor.BYTES_PER_ELEMENT;
if (!elementSize) throw Error("not a typed array");
checkMem();
var buf = U32[ ptr >>> 2];
var byteOffset = U32[(ptr + 4) >>> 2];
var byteLength = U32[(ptr + 8) >>> 2];
return new ctor(mem, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize);
}

/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
function freeArray(ptr) {
checkMem();
var buf = U32[ptr >>> 2];
memory_free(buf);
memory_free(ptr);
}

// Demangle exports and provide the usual utility on the prototype
return demangle(exports, {
get I8() { checkMem(); return I8; },
Expand All @@ -84,7 +143,10 @@ function instantiate(module, imports) {
get F32() { checkMem(); return F32; },
get F64() { checkMem(); return F64; },
newString,
getString
getString,
newArray,
getArray,
freeArray
});
}

Expand Down
8 changes: 7 additions & 1 deletion lib/loader/tests/assembly/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "allocator/arena";
import "allocator/tlsf";

export { memory };

Expand Down Expand Up @@ -44,3 +44,9 @@ export class Car {
memory.free(changetype<usize>(this));
}
}

export function sum(arr: Int32Array): i32 {
var v = 0;
for (let i = 0, k = arr.length; i < k; ++i) v += arr[i];
return v;
}
Binary file modified lib/loader/tests/build/untouched.wasm
Binary file not shown.
13 changes: 13 additions & 0 deletions lib/loader/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,16 @@ var str = "Hello world!𤭢";
var ptr = module.newString(str);
assert.strictEqual(module.getString(ptr), str);
assert.strictEqual(module.strlen(ptr), str.length);

// should be able to allocate a typed array and sum up its values
var arr = [1, 2, 3, 4, 5, 0x7fffffff];
ptr = module.newArray(new Int32Array(arr));
assert.strictEqual(module.sum(ptr), arr.reduce((a, b) => a + b, 0) | 0);

// should be able to get a view on an internal typed array
assert.deepEqual(module.getArray(Int32Array, ptr), arr);

// should be able to free and reuse the space of an internal typed array
module.freeArray(ptr);
var ptr2 = module.newArray(new Int32Array(arr));
assert.strictEqual(ptr, ptr2);
2 changes: 2 additions & 0 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6523,6 +6523,8 @@ export class Compiler extends DiagnosticEmitter {
// if present, check that the constructor is compatible with object literals
var ctor = classReference.constructorInstance;
if (ctor) {
// TODO: if the constructor requires parameters, check whether these are given as part of the
// object literal and use them to call the ctor while not generating a store.
if (ctor.signature.requiredParameters) {
this.error(
DiagnosticCode.Constructor_of_class_0_must_not_require_any_arguments,
Expand Down

0 comments on commit 9c770d8

Please sign in to comment.