Implement reference counting (#592)

This commit is contained in:
Daniel Wirtz
2019-06-05 23:15:39 +02:00
committed by GitHub
parent 3ed76a97f0
commit 0484a6b740
601 changed files with 261645 additions and 146131 deletions

View File

@ -7,7 +7,7 @@ Usage
-----
```js
const loader = require("@assemblyscript/loader");
const loader = require("assemblyscript/lib/loader");
...
```
@ -26,67 +26,164 @@ API
* **demangle**<`T`>(exports: `WasmExports`, baseModule?: `Object`): `T`<br />
Demangles an AssemblyScript module's exports to a friendly object structure. You usually don't have to call this manually as instantiation does this implicitly.
**Note:** `T` above can either be omitted if the structure of the module is unknown, or can reference a `.d.ts` (i.e. `typeof MyModule`) as produced by the compiler with the `-d` option.
**Note** that `T` above can either be omitted if the structure of the module is unknown, or can reference a `.d.ts` (i.e. `typeof MyModule`) as produced by the compiler with the `-d` option.
Instances are automatically populated with useful utility:
Besides demangling classes exported from your entry file to a handy object structure one can use like JS objects, instances are automatically populated with useful utility:
* **I8**: `Int8Array`<br />
An 8-bit signed integer view on the memory.
```ts
var value = module.I8[ptr];
```
* **U8**: `Uint8Array`<br />
An 8-bit unsigned integer view on the memory.
```ts
var value = module.U8[ptr];
```
* **I16**: `Int16Array`<br />
A 16-bit signed integer view on the memory.
```ts
var value = module.I16[ptr >>> 1];
```
* **U16**: `Uint16Array`<br />
A 16-bit unsigned integer view on the memory.
```ts
var value = module.U16[ptr >>> 1];
```
* **I32**: `Int32Array`<br />
A 32-bit signed integer view on the memory.
```ts
var value = module.I32[ptr >>> 2];
```
* **U32**: `Uint32Array`<br />
A 32-bit unsigned integer view on the memory.
```ts
var value = module.U32[ptr >>> 2];
```
* **I64**: `BigInt64Array`<br />
A 64-bit signed integer view on the memory<sup>1</sup>.
A 64-bit signed integer view on the memory, if supported by the VM.
```ts
var value = module.I64[ptr >>> 3];
```
* **U64**: `BigUint64Array`<br />
A 64-bit unsigned integer view on the memory<sup>1</sup>.
A 64-bit unsigned integer view on the memory, if supported by the VM.
```ts
var value = module.U64[ptr >>> 3];
```
* **F32**: `Float32Array`<br />
A 32-bit float view on the memory.
```ts
var value = module.I32[ptr >>> 2];
```
* **F64**: `Float64Array`<br />
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.<sup>2</sup>
```ts
var value = module.F64[ptr >>> 3];
```
* **getString**(ptr: `number`): `string`<br />
Gets a string from the module's memory by its pointer.
* **__start**(): `void`<br />
Explicit start function if the `--explicitStart` option is used. Must be called before any other exports if present.
* **newArray**(view: `TypedArray`, length?: `number`, unsafe?: `boolean`): `number`<br />
Copies a typed array into the module's memory and returns its pointer.<sup>2</sup>
* **__allocString**(str: `string`): `number`<br />
Allocates a new string in the module's memory and returns a reference (pointer) to it.
* **newArray**(ctor: `TypedArrayConstructor`, length: `number`, unsafe?: `boolean`): `number`<br />
Creates a typed array in the module's memory and returns its pointer.<sup>2</sup>
```ts
var ref = module.__retain(module.__allocString("hello world"));
...
module.__release(ref);
```
* **getArray**(ctor: `TypedArrayConstructor`, ptr: `number`): `TypedArray`<br />
Gets a view on a typed array in the module's memory by its pointer.
* **__getString**(ref: `number`): `string`<br />
Reads (copies) the value of a string from the module's memory.
* **freeArray**(ptr: `number`): `void`<br />
Frees a typed array in the module's memory. Must not be accessed anymore afterwards.
```ts
var str = module.__getString(ref);
...
```
* **getFunction**(ptr: `number`): `function`<br />
Gets a function by its pointer.
* **__allocArray**(id: `number`, values: `number[]`): `number`<br />
Allocates a new array in the module's memory and returns a reference (pointer) to it.
Automatically retains interior pointers. The `id` is the unique runtime id of the respective array class. If you are using `Int32Array` for example, the best way to know the id is an `export const INT32ARRAY_ID = idof<Int32Array>()`. When done with the array, make sure to release it.
* **newFunction**(fn: `function`): `number`<br />
Creates a new function in the module's table and returns its pointer. Note that only actual
WebAssembly functions, i.e. as exported by the module, are supported.
```ts
var ref = module.__retain(module.__allocArray(module.INT32ARRAY, [1, 2, 3]));
...
module.__release(ref);
```
<sup>1</sup> This feature has not yet landed in any VM as of this writing.<br />
<sup>2</sup> Requires that memory utilities have been exported through `export { memory }` within the entry file.
* **__getArray**(ref: `number`): `number[]`<br />
Reads (copies) the values of an array from the module's memory.
```ts
var arr = module.__getArray(ref);
...
```
* **__getArrayView**(ref: `number`): `TypedArray`<br />
Gets a view on the values of an array in the module's memory. This differs from `__getArray` in that the data isn't copied but remains *live* in both directions. That's faster but also unsafe because if the array grows or becomes released, the view will no longer represent the correct memory region and modifying its values in this state will most likely corrupt memory. Use, but use with care.
* **__retain**(ref: `number`): `number`<br />
Retains a reference externally, making sure that it doesn't become collected prematurely. Returns the reference.
* **__release**(ref: `number`): `void`<br />
Releases a previously retained reference to an object, allowing the runtime to collect it once its reference count reaches zero.
* **__alloc**(size: `number`, id: `number`): `number`<br />
Allocates an instance of the class represented by the specified id. If you are using `MyClass` for example, the best way to know the id and the necessary size is an `export const MYCLASS_ID = idof<MyClass>()` and an `export const MYCLASS_SIZE = offsetof<MyClass>()`. Afterwards, use the respective views to assign values to the class's memory while making sure to retain interior references to other managed objects once. When done with the class, make sure to release it, which will automatically release any interior references once the class becomes collected.
```ts
var ref = module.__retain(module.__alloc(module.MYCLASS_SIZE, module.MYCLASS_ID));
F32[ref + MYCLASS_BASICFIELD1_OFFSET >>> 2] = field1_value_f32;
U32[ref + MYCLASS_MANAGEDFIELD2_OFFSET >>> 2] = module.__retain(field2_value_ref);
...
module.__release(ref);
```
* **__instanceof**(ref: `number`, baseId: `number`): `boolean`<br />
Tests whether an object is an instance of the class represented by the specified base id.
```ts
if (module.__instanceof(ref, module.MYCLASS_ID)) {
...
}
```
* **__collect**(): `void`<br />
Forces a cycle collection. Only relevant if objects potentially forming reference cycles are used.
**Note** that the views like `I32` above will automatically be updated when the module's memory grows. Don't cache these if this can happen.
**Note** that the allocation and ownership features above require the `full` (this is the default) or the `stub` runtime to be present in your module. Other runtime variations do not export this functionality without further ado (so the compiler can eliminate what's dead code).
**Note** that references returned from exported functions have already been retained for you and the runtime expects that you release them once not needed anymore. This is also true for constructors and getters.
**Beware that your module is likely going to explode with seemingly random errors when using the allocation and ownership features incorrectly!**
* Use the correct ids, sizes and layouts (C-style non-packed, export `offsetof<MyClass>("myField")` in case of doubt)
* Clear unused memory to zero
* Retain what you allocate
* Retain interior pointers (except in `__allocArray`)
* Don't retain what's returned (is already retained for you)
* Release when you're done with something and don't ever use it again
Examples
--------
@ -101,58 +198,6 @@ const myModule = loader.instantiateBuffer(fs.readFileSync("myModule.wasm"), myIm
const myModule = await loader.instantiateStreaming(fetch("myModule.wasm"), myImports);
```
### Reading/writing basic values to/from memory
```js
var ptrToInt8 = ...;
var value = myModule.I8[ptrToInt8]; // alignment of log2(1)=0
var ptrToInt16 = ...;
var value = myModule.I16[ptrToInt16 >>> 1]; // alignment of log2(2)=1
var ptrToInt32 = ...;
var value = myModule.I32[ptrToInt32 >>> 2]; // alignment of log2(4)=2
var ptrToInt64 = ...;
var value = myModule.I64[ptrToInt64 >>> 3]; // alignment of log2(8)=3
var ptrToFloat32 = ...;
var value = myModule.F32[ptrToFloat32 >>> 2]; // alignment of log2(4)=2
var ptrToFloat64 = ...;
var value = myModule.F64[ptrToFloat64 >>> 3]; // alignment of log2(8)=3
// Likewise, for writing
myModule.I8[ptrToInt8] = newValue;
myModule.I16[ptrToInt16 >>> 1] = newValue;
myModule.I32[ptrToInt32 >>> 2] = newValue;
myModule.I64[ptrToInt64 >>> 3] = newValue;
myModule.F32[ptrToFloat32 >>> 2] = newValue;
myModule.F64[ptrToFloat64 >>> 3] = newValue;
```
**Note:** Make sure to reference the views as shown above as these will automatically be updated when the module's memory grows.
### Working with strings and arrays
Strings and arrays cannot yet flow in and out of WebAssembly naturally, hence it is necessary to create them in the module's memory using the `newString` and `newArray` helpers. Afterwards, instead of passing the string or array directly, the resulting reference (pointer) is provided instead:
```js
var str = "Hello world!";
var ptr = module.newString(str);
// ... do something with ptr, i.e. call a WebAssembly export ...
```
Similarly, when a string or array is returned from a WebAssembly function, a reference (pointer) is received on the JS side and the `getString` and `getArray` helpers can be used to obtain their values:
```js
var ptrToString = ...;
var str = module.getString(ptrToString);
// ... do something with str ...
```
### Usage with TypeScript definitions produced by the compiler
```ts

60
lib/loader/index.d.ts vendored
View File

@ -1,18 +1,19 @@
import "@types/webassembly-js-api";
/** WebAssembly imports with two levels of nesting. */
interface ImportsObject {
[key: string]: {},
env: {
interface ImportsObject extends Record<string, any> {
env?: {
memory?: WebAssembly.Memory,
table?: WebAssembly.Table,
abort?: (msg: number, file: number, line: number, column: number) => void
abort?: (msg: number, file: number, line: number, column: number) => void,
trace?: (msg: number, numArgs?: number, ...args: any[]) => void
}
}
type TypedArray
= Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
@ -20,16 +21,6 @@ type TypedArray
| 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. */
@ -52,25 +43,28 @@ interface ASUtil {
readonly F32: Float32Array;
/** A 64-bit float view on the memory. */
readonly F64: Float64Array;
/** Allocates a new string in the module's memory and returns its pointer. */
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<T extends TypedArray = TypedArray>(ctor: TypedArrayConstructor, ptr: number): T;
/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
freeArray(ptr: number): void;
/** Gets a function by its pointer. */
getFunction<R = any>(ptr: number): (...args: any[]) => R;
/**
* Creates a new function in the module's table and returns its pointer. Note that only actual
* WebAssembly functions, i.e. as exported by the module, are supported.
*/
newFunction(fn: (...args: any[]) => any): number;
/** Explicit start function, if requested. */
__start(): void;
/** Allocates a new string in the module's memory and returns a reference (pointer) to it. */
__allocString(str: string): number;
/** Reads (copies) the value of a string from the module's memory. */
__getString(ref: number): string;
/** Allocates a new array in the module's memory and returns a reference (pointer) to it. */
__allocArray(id: number, values: number[]): number;
/** Reads (copies) the values of an array from the module's memory. */
__getArray(ref: number): number[];
/** Gets a view on the values of an array in the module's memory. */
__getArrayView(ref: number): TypedArray;
/** 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;
/** Forces a cycle collection. Only relevant if objects potentially forming reference cycles are used. */
__collect(): void;
}
/** Instantiates an AssemblyScript module using the specified imports. */

View File

@ -1,43 +1,75 @@
"use strict";
const hasBigInt64 = typeof BigUint64Array !== "undefined";
const thisPtr = Symbol();
// Runtime header offsets
const ID_OFFSET = -8;
const SIZE_OFFSET = -4;
// Runtime ids
const ARRAYBUFFER_ID = 0;
const STRING_ID = 1;
const ARRAYBUFFERVIEW_ID = 2;
// Runtime type information
const ARRAYBUFFERVIEW = 1 << 0;
const ARRAY = 1 << 1;
const SET = 1 << 2;
const MAP = 1 << 3;
const VAL_ALIGN = 1 << 5;
const VAL_SIGNED = 1 << 10;
const VAL_FLOAT = 1 << 11;
const VAL_NULLABLE = 1 << 12;
const VAL_MANAGED = 1 << 13;
const KEY_ALIGN = 1 << 14;
const KEY_SIGNED = 1 << 19;
const KEY_FLOAT = 1 << 20;
const KEY_NULLABLE = 1 << 21;
const KEY_MANAGED = 1 << 22;
// Array(BufferView) layout
const ARRAYBUFFERVIEW_BUFFER_OFFSET = 0;
const ARRAYBUFFERVIEW_DATASTART_OFFSET = 4;
const ARRAYBUFFERVIEW_DATALENGTH_OFFSET = 8;
const ARRAYBUFFERVIEW_SIZE = 12;
const ARRAY_LENGTH_OFFSET = 12;
const ARRAY_SIZE = 16;
const BIGINT = typeof BigUint64Array !== "undefined";
const THIS = Symbol();
const CHUNKSIZE = 1024;
/** Gets a string from an U32 and an U16 view on a memory. */
function getStringImpl(U32, U16, ptr) {
var dataLength = U32[ptr >>> 2];
var dataOffset = (ptr + 4) >>> 1;
var dataRemain = dataLength;
var parts = [];
const chunkSize = 1024;
while (dataRemain > chunkSize) {
let last = U16[dataOffset + chunkSize - 1];
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize;
let part = U16.subarray(dataOffset, dataOffset += size);
parts.push(String.fromCharCode.apply(String, part));
dataRemain -= size;
}
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
function getStringImpl(U32, U16, ref) {
var length = U32[(ref + SIZE_OFFSET) >>> 2] >>> 1;
var offset = ref >>> 1;
if (length <= CHUNKSIZE) return String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
const parts = [];
do {
const last = U16[offset + CHUNKSIZE - 1];
const size = last >= 0xD800 && last < 0xDC00 ? CHUNKSIZE - 1 : CHUNKSIZE;
parts.push(String.fromCharCode.apply(String, U16.subarray(offset, offset += size)));
length -= size;
} while (length > CHUNKSIZE);
return parts.join("") + String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
}
/** Prepares the base module prior to instantiation. */
function preInstantiate(imports) {
var baseModule = {};
const baseModule = {};
function getString(memory, ptr) {
function getString(memory, ref) {
if (!memory) return "<yet unknown>";
var buffer = memory.buffer;
return getStringImpl(new Uint32Array(buffer), new Uint16Array(buffer), ptr);
const buffer = memory.buffer;
return getStringImpl(new Uint32Array(buffer), new Uint16Array(buffer), ref);
}
// add common imports used by stdlib for convenience
var env = (imports.env = imports.env || {});
const env = (imports.env = imports.env || {});
env.abort = env.abort || function abort(mesg, file, line, colm) {
var memory = baseModule.memory || env.memory; // prefer exported, otherwise try imported
const memory = baseModule.memory || env.memory; // prefer exported, otherwise try imported
throw Error("abort: " + getString(memory, mesg) + " at " + getString(memory, file) + ":" + line + ":" + colm);
}
env.trace = env.trace || function trace(mesg, n) {
var memory = baseModule.memory || env.memory;
const memory = baseModule.memory || env.memory;
console.log("trace: " + getString(memory, mesg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", "));
}
imports.Math = imports.Math || Math;
@ -48,13 +80,12 @@ function preInstantiate(imports) {
/** Prepares the final module once instantiation is complete. */
function postInstantiate(baseModule, instance) {
var rawExports = instance.exports;
var memory = rawExports.memory;
var memory_allocate = rawExports["memory.allocate"];
var memory_fill = rawExports["memory.fill"];
var memory_free = rawExports["memory.free"];
var table = rawExports.table;
var setargc = rawExports._setargc || function() {};
const rawExports = instance.exports;
const memory = rawExports.memory;
const table = rawExports.table;
const alloc = rawExports["__alloc"];
const retain = rawExports["__retain"];
const rttiBase = rawExports["__rtti_base"] || ~0; // oob if not present
// Provide views for all sorts of basic values
var buffer, I8, U8, I16, U16, I32, U32, F32, F64, I64, U64;
@ -70,7 +101,7 @@ function postInstantiate(baseModule, instance) {
U16 = new Uint16Array(buffer);
I32 = new Int32Array(buffer);
U32 = new Uint32Array(buffer);
if (hasBigInt64) {
if (BIGINT) {
I64 = new BigInt64Array(buffer);
U64 = new BigUint64Array(buffer);
}
@ -80,109 +111,120 @@ function postInstantiate(baseModule, instance) {
}
checkMem();
/** Allocates a new string in the module's memory and returns its pointer. */
function newString(str) {
var dataLength = str.length;
var ptr = memory_allocate(4 + (dataLength << 1));
var dataOffset = (4 + ptr) >>> 1;
checkMem();
U32[ptr >>> 2] = dataLength;
for (let i = 0; i < dataLength; ++i) U16[dataOffset + i] = str.charCodeAt(i);
return ptr;
/** Gets the runtime type info for the given id. */
function getInfo(id) {
const count = U32[rttiBase >>> 2];
if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
return U32[(rttiBase + 4 >>> 2) + id * 2];
}
baseModule.newString = newString;
/** Gets a string from the module's memory by its pointer. */
function getString(ptr) {
checkMem();
return getStringImpl(U32, U16, ptr);
/** Gets the runtime base id for the given id. */
function getBase(id) {
const count = U32[rttiBase >>> 2];
if ((id >>>= 0) >= count) throw Error("invalid id: " + id);
return U32[(rttiBase + 4 >>> 2) + id * 2 + 1];
}
baseModule.getString = getString;
function computeBufferSize(byteLength) {
const HEADER_SIZE = 8;
return 1 << (32 - Math.clz32(byteLength + HEADER_SIZE - 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
}
/** 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
/** 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();
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(buffer, buf + 8, length).set(view);
if (view.length < length && !unsafe) {
let setLength = elementSize * view.length;
memory_fill(buf + 8 + setLength, 0, byteLength - setLength);
for (let i = 0, j = ref >>> 1; i < length; ++i) U16[j + 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];
if (id !== STRING_ID) throw Error("not a string: " + ref);
return getStringImpl(U32, U16, ref);
}
baseModule.__getString = __getString;
/** Gets the view matching the specified alignment, signedness and floatness. */
function getView(align, signed, float) {
if (float) {
switch (align) {
case 2: return F32;
case 3: return F64;
}
} else {
switch (align) {
case 0: return signed ? I8 : U8;
case 1: return signed ? I16 : U16;
case 2: return signed ? I32 : U32;
case 3: return signed ? I64 : U64;
}
} else if (!unsafe) {
memory_fill(buf + 8, 0, byteLength);
}
return ptr;
throw Error("unsupported align: " + align);
}
baseModule.newArray = newArray;
/** Gets a view on a typed array in the module's memory by its pointer. */
function getArray(ctor, ptr) {
var elementSize = ctor.BYTES_PER_ELEMENT;
if (!elementSize) throw Error("not a typed array");
/** Allocates a new array in the module's memory and returns its retained pointer. */
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 length = values.length;
const buf = alloc(length << align, ARRAYBUFFER_ID);
const arr = alloc(info & ARRAY ? ARRAY_SIZE : ARRAYBUFFERVIEW_SIZE, id);
checkMem();
var buf = U32[ ptr >>> 2];
var byteOffset = U32[(ptr + 4) >>> 2];
var byteLength = U32[(ptr + 8) >>> 2];
return new ctor(buffer, buf + 8 + byteOffset, (byteLength - byteOffset) / elementSize);
U32[arr + ARRAYBUFFERVIEW_BUFFER_OFFSET >>> 2] = retain(buf);
U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2] = buf;
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]);
return arr;
}
baseModule.getArray = getArray;
baseModule.__allocArray = __allocArray;
/** Frees a typed array in the module's memory. Must not be accessed anymore afterwards. */
function freeArray(ptr) {
/** Gets a view on the values of an array in the module's memory. */
function __getArrayView(arr) {
checkMem();
var buf = U32[ptr >>> 2];
memory_free(buf);
memory_free(ptr);
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);
var buf = U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2];
const length = info & ARRAY
? U32[arr + ARRAY_LENGTH_OFFSET >>> 2]
: U32[buf + SIZE_OFFSET >>> 2] >>> align;
return getView(align, info & VAL_SIGNED, info & VAL_FLOAT)
.slice(buf >>>= align, buf + length);
}
baseModule.freeArray = freeArray;
baseModule.__getArrayView = __getArrayView;
/**
* Creates a new function in the module's table and returns its pointer. Note that only actual
* WebAssembly functions, i.e. as exported by the module, are supported.
*/
function newFunction(fn) {
if (typeof fn.original === "function") fn = fn.original;
var index = table.length;
table.grow(1);
table.set(index, fn);
return index;
/** Reads (copies) the values of an array from the module's memory. */
function __getArray(arr) {
return Array.from(__getArrayView(arr));
}
baseModule.newFunction = newFunction;
baseModule.__getArray = __getArray;
/** Gets a function by its pointer. */
function getFunction(ptr) {
return wrapFunction(table.get(ptr), setargc);
/** 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];
if (id <= U32[rttiBase >>> 2]) {
do if (id == baseId) return true;
while (id = getBase(id));
}
return false;
}
baseModule.getFunction = getFunction;
baseModule.__instanceof = __instanceof;
// Pull basic exports to baseModule so code in preInstantiate can use them
baseModule.memory = baseModule.memory || memory;
@ -209,8 +251,6 @@ function wrapFunction(fn, setargc) {
setargc(args.length);
return fn(...args);
}
// adding a function to the table with `newFunction` is limited to actual WebAssembly functions,
// hence we can't use the wrapper and instead need to provide a reference to the original
wrap.original = fn;
return wrap;
}
@ -245,7 +285,7 @@ exports.instantiateStreaming = instantiateStreaming;
/** Demangles an AssemblyScript module's exports to a friendly object structure. */
function demangle(exports, baseModule) {
var module = baseModule ? Object.create(baseModule) : {};
var setargc = exports._setargc || function() {};
var setargc = exports["__setargc"] || function() {};
function hasOwnProperty(elem, prop) {
return Object.prototype.hasOwnProperty.call(elem, prop);
}
@ -268,9 +308,13 @@ function demangle(exports, baseModule) {
let ctor = function(...args) {
return ctor.wrap(ctor.prototype.constructor(0, ...args));
};
ctor.prototype = {};
ctor.prototype = {
valueOf: function valueOf() {
return this[THIS];
}
};
ctor.wrap = function(thisValue) {
return Object.create(ctor.prototype, { [thisPtr]: { value: thisValue, writable: false } });
return Object.create(ctor.prototype, { [THIS]: { value: thisValue, writable: false } });
};
if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name))
@ -284,8 +328,8 @@ function demangle(exports, baseModule) {
let getter = exports[internalName.replace("set:", "get:")];
let setter = exports[internalName.replace("get:", "set:")];
Object.defineProperty(curr, name, {
get: function() { return getter(this[thisPtr]); },
set: function(value) { setter(this[thisPtr], value); },
get: function() { return getter(this[THIS]); },
set: function(value) { setter(this[THIS], value); },
enumerable: true
});
}
@ -296,7 +340,7 @@ function demangle(exports, baseModule) {
Object.defineProperty(curr, name, {
value: function (...args) {
setargc(args.length);
return elem(this[thisPtr], ...args);
return elem(this[THIS], ...args);
}
});
}

View File

@ -1,5 +1,3 @@
import "allocator/tlsf";
export { memory };
export const COLOR: string = "red";
@ -39,10 +37,6 @@ export class Car {
this.doorsOpen = false;
return true;
}
dispose(): void {
memory.free(changetype<usize>(this));
}
}
export function sum(arr: Int32Array): i32 {
@ -51,11 +45,15 @@ export function sum(arr: Int32Array): i32 {
return v;
}
export function changeLength(arr: Array<i32>, length: i32): void {
arr.length = length;
}
export function varadd(a: i32 = 1, b: i32 = 2): i32 {
return a + b;
}
export const varadd_ptr = varadd;
export const varadd_ref = varadd;
export function calladd(fn: (a: i32, b: i32) => i32, a: i32, b: i32): i32 {
return fn(a, b);
@ -64,3 +62,8 @@ export function calladd(fn: (a: i32, b: i32) => i32, a: i32, b: i32): i32 {
export function dotrace(num: f64): void {
trace("The answer is", 1, num);
}
export const INT32ARRAY_ID = idof<Int32Array>();
export const UINT32ARRAY_ID = idof<Uint32Array>();
export const FLOAT32ARRAY_ID = idof<Float32Array>();
export const ARRAYI32_ID = idof<Array<i32>>();

View File

@ -21,44 +21,87 @@ assert(proto.F64 instanceof Float64Array);
// should export memory
assert(module.memory instanceof WebAssembly.Memory);
assert(typeof module.memory.free === "function");
assert(typeof module.memory.copy === "function");
// should be able to get an exported string
assert.strictEqual(module.getString(module.COLOR), "red");
assert.strictEqual(module.__getString(module.COLOR), "red");
// should be able to allocate and work with a new string
var str = "Hello world!𤭢";
var ptr = module.newString(str);
assert.strictEqual(module.getString(ptr), str);
assert.strictEqual(module.strlen(ptr), str.length);
{
let str = "Hello world!𤭢";
let ref = module.__retain(module.__allocString(str));
assert.strictEqual(module.__getString(ref), str);
assert.strictEqual(module.strlen(ref), str.length);
module.__release(ref);
}
// 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 allocate a typed array
{
var arr = [1, 2, 3, 4, 5, 0x80000000 | 0];
let ref = module.__retain(module.__allocArray(module.INT32ARRAY_ID, arr));
assert(module.__instanceof(ref, module.INT32ARRAY_ID));
// should be able to get a view on an internal typed array
assert.deepEqual(module.getArray(Int32Array, ptr), arr);
// should be able to get the values of an array
assert.deepEqual(module.__getArray(ref), 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);
// should be able to get a view on an array
assert.deepEqual(module.__getArrayView(ref), new Int32Array(arr));
// should be able to just call a function with variable arguments
// should be able to sum up its values
assert.strictEqual(module.sum(ref), arr.reduce((a, b) => (a + b) | 0, 0) | 0);
// should be able to release no longer needed references
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.__getArray(ref), arr);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
}
// should be able to distinguish between integer and float
{
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.__getArray(ref), arr);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
}
// should be able to work with normal arrays
{
let arr = [1, 2, 3, 4, 5];
let ref = module.__retain(module.__allocArray(module.ARRAYI32_ID, arr));
assert(module.__instanceof(ref, module.ARRAYI32_ID));
module.changeLength(ref, 3);
assert.deepEqual(module.__getArray(ref), [1, 2, 3]);
module.__release(ref);
try { module.__release(ref); assert(false); } catch (e) {};
}
// should be able to correctly call a function with variable arguments
assert.strictEqual(module.varadd(), 3);
assert.strictEqual(module.varadd(2, 3), 5);
assert.strictEqual(module.varadd(2), 4);
// TBD: table is no more exported by default to allow more optimizations
// should be able to get a function from the table and just call it with variable arguments
var fn = module.getFunction(module.varadd_ptr);
assert.strictEqual(fn(), 3);
assert.strictEqual(fn(2, 3), 5);
assert.strictEqual(fn(2), 4);
// var fn = module.getFunction(module.varadd_ref);
// assert.strictEqual(fn(), 3);
// assert.strictEqual(fn(2, 3), 5);
// assert.strictEqual(fn(2), 4);
// should be able to create a new function and call it from WASM
ptr = module.newFunction(module.varadd);
assert.strictEqual(module.calladd(ptr, 2, 3), 5);
// ref = module.newFunction(module.varadd);
// assert.strictEqual(module.calladd(ref, 2, 3), 5);
// should be able to use a class
var car = new module.Car(5);
@ -68,6 +111,7 @@ car.openDoors();
assert.strictEqual(car.isDoorsOpen, 1);
car.closeDoors();
assert.strictEqual(car.isDoorsOpen, 0);
module.__release(car); // uses Car.prototype.valueOf to obtain `thisPtr`
// should be able to use trace
module.dotrace(42);
module.dotrace(42);