mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-13 06:51:34 +00:00
Initial loader that unifies utils and demangle
This commit is contained in:
132
lib/loader/README.md
Normal file
132
lib/loader/README.md
Normal file
@ -0,0 +1,132 @@
|
||||
 loader
|
||||
======================
|
||||
|
||||
A convenient loader for AssemblyScript modules. Demangles module exports to a friendly object structure compatible with WebIDL and TypeScript definitions and provides some useful utility to read/write data from/to memory.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```js
|
||||
const loader = require("@assemblyscript/loader");
|
||||
...
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
* **instantiate**<`T`>(module: `WebAssembly.Module`, imports?: `WasmImports`): `ASModule<T>`<br />
|
||||
Instantiates an AssemblyScript module using the specified imports.
|
||||
|
||||
* **instantiateBuffer**<`T`>(buffer: `Uint8Array`, imports?: `WasmImports`): `ASModule<T>`<br />
|
||||
Instantiates an AssemblyScript module from a buffer using the specified imports.
|
||||
|
||||
* **instantiateStreaming**<`T`>(response: `Response`, imports?: `WasmImports`): `Promise<ASModule<T>>`<br />
|
||||
Instantiates an AssemblyScript module from a response using the sspecified imports.
|
||||
|
||||
* **demangle**<`T`>(exports: `WasmExports`): `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` as produced by the compiler with the `-d` option.
|
||||
|
||||
Instances of `ASModule` are automatically populated with some useful utility:
|
||||
|
||||
* **I8**: `Int8Array`<br />
|
||||
An 8-bit signed integer view on the memory.
|
||||
|
||||
* **U8**: `Uint8Array`<br />
|
||||
An 8-bit unsigned integer view on the memory.
|
||||
|
||||
* **I16**: `Int16Array`<br />
|
||||
A 16-bit signed integer view on the memory.
|
||||
|
||||
* **U16**: `Uint16Array`<br />
|
||||
A 16-bit unsigned integer view on the memory.
|
||||
|
||||
* **I32**: `Int32Array`<br />
|
||||
A 32-bit signed integer view on the memory.
|
||||
|
||||
* **U32**: `Uint32Array`<br />
|
||||
A 32-bit unsigned integer view on the memory.
|
||||
|
||||
* **F32**: `Float32Array`<br />
|
||||
A 32-bit float view on the memory.
|
||||
|
||||
* **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. Requires `allocate_memory` to be exported from your module's entry file, i.e.:
|
||||
|
||||
```js
|
||||
import "allocator/tlsf";
|
||||
export { allocate_memory, free_memory };
|
||||
```
|
||||
|
||||
* **getString**(ptr: `number`): `string`<br />
|
||||
Gets a string from the module's memory by its pointer.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
### Instantiating a module
|
||||
|
||||
```js
|
||||
// From a module provided as a buffer, i.e. as returned by fs.readFileSync
|
||||
const myModule = loader.instatiateBuffer(fs.readFileSync("myModule.wasm"), myImports);
|
||||
|
||||
// From a response object, i.e. as returned by window.fetch
|
||||
const myModule = await loader.instantiateStreaming(fetch("myModule.wasm"), myImports);
|
||||
```
|
||||
|
||||
### Reading/writing basic values to/from memory
|
||||
|
||||
```js
|
||||
var ptrToInt8 = ...;
|
||||
var value = myModule.U16[ptrToInt8]; // alignment of log2(1)=0
|
||||
|
||||
var ptrToInt16 = ...;
|
||||
var value = myModule.U16[ptrToInt16 >>> 1]; // alignment of log2(2)=1
|
||||
|
||||
var ptrToInt32 = ...;
|
||||
var value = myModule.U32[ptrToInt32 >>> 2]; // alignment of log2(4)=2
|
||||
|
||||
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.U16[ptrToInt8] = newValue;
|
||||
myModule.U16[ptrToInt16 >>> 1] = newValue;
|
||||
myModule.U32[ptrToInt32 >>> 2] = 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.
|
||||
|
||||
### Allocating/obtaining strings to/from memory
|
||||
|
||||
```js
|
||||
// Allocating a string, i.e. to be passed to an export expecting one
|
||||
var str = "Hello world!";
|
||||
var ptr = module.newString(str);
|
||||
|
||||
// Disposing a string that is no longer needed (requires free_memory to be exported)
|
||||
module.free_memory(ptr);
|
||||
|
||||
// Obtaining a string, i.e. as returned by an export
|
||||
var ptrToString = ...;
|
||||
var str = module.getString(ptrToString);
|
||||
```
|
||||
|
||||
### Usage with TypeScript definitions produced by the compiler
|
||||
|
||||
```ts
|
||||
import MyModule from "myModule"; // pointing at the d.ts
|
||||
|
||||
const myModule = loader.instatiateBuffer<MyModule>(fs.readFileSync("myModule.wasm"), myImports);
|
||||
```
|
||||
|
||||
**Hint:** You can produce a `.d.ts` for your module with the `-d` option on the command line.
|
47
lib/loader/index.d.ts
vendored
Normal file
47
lib/loader/index.d.ts
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
import "@types/webassembly-js-api";
|
||||
|
||||
/** WebAssembly imports with two levels of nesting. */
|
||||
interface ImportsObject {
|
||||
[key: string]: {},
|
||||
env: {
|
||||
memory?: WebAssembly.Memory,
|
||||
table?: WebAssembly.Table,
|
||||
abort?: (msg: number, file: number, line: number, column: number) => void
|
||||
}
|
||||
}
|
||||
|
||||
/** Utility mixed in by the loader. */
|
||||
interface ASUtil {
|
||||
/** An 8-bit signed integer view on the memory. */
|
||||
readonly I8: Uint8Array,
|
||||
/** An 8-bit unsigned integer view on the memory. */
|
||||
readonly U8: Uint8Array,
|
||||
/** A 16-bit signed integer view on the memory. */
|
||||
readonly I16: Uint16Array,
|
||||
/** A 16-bit unsigned integer view on the memory. */
|
||||
readonly U16: Uint16Array,
|
||||
/** A 32-bit signed integer view on the memory. */
|
||||
readonly I32: Uint32Array,
|
||||
/** A 32-bit unsigned integer view on the memory. */
|
||||
readonly U32: Uint32Array,
|
||||
/** A 32-bit float view on the memory. */
|
||||
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;
|
||||
}
|
||||
|
||||
/** Instantiates an AssemblyScript module using the specified imports. */
|
||||
export declare function instantiate<T extends {}>(module: WebAssembly.Module, imports?: ImportsObject): ASUtil & T;
|
||||
|
||||
/** Instantiates an AssemblyScript module from a buffer using the specified imports. */
|
||||
export declare function instantiateBuffer<T extends {}>(buffer: Uint8Array, imports?: ImportsObject): ASUtil & T;
|
||||
|
||||
/** Instantiates an AssemblyScript module from a response using the sspecified imports. */
|
||||
export declare function instantiateStreaming<T extends {}>(response: Response, imports?: ImportsObject): Promise<ASUtil & T>;
|
||||
|
||||
/** Demangles an AssemblyScript module's exports to a friendly object structure. */
|
||||
export declare function demangle<T extends {}>(exports: {}): T;
|
163
lib/loader/index.js
Normal file
163
lib/loader/index.js
Normal file
@ -0,0 +1,163 @@
|
||||
"use strict";
|
||||
|
||||
/** Instantiates an AssemblyScript module using the specified imports. */
|
||||
function instantiate(module, imports) {
|
||||
|
||||
// Set up the imports object
|
||||
if (!imports) imports = {};
|
||||
if (!imports.env) imports.env = {};
|
||||
if (!imports.env.abort) imports.env.abort = function(mesg, file, line, colm) {
|
||||
mesg = mem ? getString(mesg) : "";
|
||||
file = mem ? getString(file) : "<instantiate>";
|
||||
throw Error("abort: " + mesg + " at " + file + ":" + line + ":" + colm);
|
||||
}
|
||||
|
||||
// Instantiate the module and obtain its (flat) exports
|
||||
var instance = new WebAssembly.Instance(module, imports);
|
||||
var exports = instance.exports;
|
||||
|
||||
// Provide views for all sorts of basic values
|
||||
var mem, I8, U8, I16, U16, I32, U32, F32, F64;
|
||||
|
||||
/** Updates memory views if memory has grown meanwhile. */
|
||||
function checkMem() {
|
||||
// see: https://github.com/WebAssembly/design/issues/1210
|
||||
if (mem !== exports.memory.buffer) {
|
||||
mem = exports.memory.buffer;
|
||||
I8 = new Int8Array(mem);
|
||||
U8 = new Uint8Array(mem);
|
||||
I16 = new Int16Array(mem);
|
||||
U16 = new Uint16Array(mem);
|
||||
I32 = new Int32Array(mem);
|
||||
U32 = new Uint32Array(mem);
|
||||
F32 = new Float32Array(mem);
|
||||
F64 = new Float64Array(mem);
|
||||
}
|
||||
}
|
||||
checkMem();
|
||||
|
||||
/** Allocates a new string in the module's memory and returns its pointer. */
|
||||
function newString(str) {
|
||||
var dataLength = str.length;
|
||||
var ptr = exports.allocate_memory(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 a string from the module's memory by its pointer. */
|
||||
function getString(ptr) {
|
||||
checkMem();
|
||||
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));
|
||||
}
|
||||
|
||||
// Demangle exports and provide the usual utility on the prototype
|
||||
return demangle(exports, {
|
||||
get I8() { checkMem(); return I8; },
|
||||
get U8() { checkMem(); return U8; },
|
||||
get I16() { checkMem(); return I16; },
|
||||
get U16() { checkMem(); return U16; },
|
||||
get I32() { checkMem(); return I32; },
|
||||
get U32() { checkMem(); return U32; },
|
||||
get F32() { checkMem(); return F32; },
|
||||
get F64() { checkMem(); return F64; },
|
||||
newString,
|
||||
getString
|
||||
});
|
||||
}
|
||||
|
||||
exports.instantiate = instantiate;
|
||||
|
||||
/** Instantiates an AssemblyScript module from a buffer using the specified imports. */
|
||||
function instantiateBuffer(buffer, imports) {
|
||||
var module = new WebAssembly.Module(buffer);
|
||||
return instantiate(module, imports);
|
||||
}
|
||||
|
||||
exports.instantiateBuffer = instantiateBuffer;
|
||||
|
||||
/** Instantiates an AssemblyScript module from a response using the specified imports. */
|
||||
async function instantiateStreaming(response, imports) {
|
||||
var module = await WebAssembly.compileStreaming(response);
|
||||
return instantiate(module, imports);
|
||||
}
|
||||
|
||||
exports.instantiateStreaming = instantiateStreaming;
|
||||
|
||||
/** Demangles an AssemblyScript module's exports to a friendly object structure. */
|
||||
function demangle(exports, baseModule) {
|
||||
var module = baseModule ? Object.create(baseModule) : {};
|
||||
function hasOwnProperty(elem, prop) {
|
||||
return Object.prototype.hasOwnProperty.call(elem, prop);
|
||||
}
|
||||
for (let internalName in exports) {
|
||||
if (!hasOwnProperty(exports, internalName)) continue;
|
||||
let elem = exports[internalName];
|
||||
let parts = internalName.split(".");
|
||||
let curr = module;
|
||||
while (parts.length > 1) {
|
||||
let part = parts.shift();
|
||||
if (!hasOwnProperty(curr, part)) curr[part] = {};
|
||||
curr = curr[part];
|
||||
}
|
||||
let name = parts[0];
|
||||
let hash = name.indexOf("#");
|
||||
if (hash >= 0) {
|
||||
let className = name.substring(0, hash);
|
||||
let classElem = curr[className];
|
||||
if (typeof classElem === "undefined" || !classElem.prototype) {
|
||||
let ctor = function(...args) {
|
||||
return ctor.wrap(ctor.prototype.constructor(...args));
|
||||
};
|
||||
ctor.prototype = {};
|
||||
ctor.wrap = function(thisValue) {
|
||||
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))
|
||||
);
|
||||
curr[className] = ctor;
|
||||
}
|
||||
name = name.substring(hash + 1);
|
||||
curr = curr[className].prototype;
|
||||
if (/^(get|set):/.test(name)) {
|
||||
if (!hasOwnProperty(curr, name = name.substring(4))) {
|
||||
let getter = exports[internalName.replace("set:", "get:")];
|
||||
let setter = exports[internalName.replace("get:", "set:")];
|
||||
Object.defineProperty(curr, name, {
|
||||
get: function() { return getter(this.this); },
|
||||
set: function(value) { setter(this.this, value); },
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
} else curr[name] = function(...args) { return elem(this.this, ...args); };
|
||||
} else {
|
||||
if (/^(get|set):/.test(name)) {
|
||||
if (!hasOwnProperty(curr, name = name.substring(4))) {
|
||||
Object.defineProperty(curr, name, {
|
||||
get: exports[internalName.replace("set:", "get:")],
|
||||
set: exports[internalName.replace("get:", "set:")],
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
} else curr[name] = elem;
|
||||
}
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
exports.demangle = demangle;
|
18
lib/loader/package.json
Normal file
18
lib/loader/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@assemblyscript/loader",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"test": "node tests"
|
||||
},
|
||||
"files": [
|
||||
"index.d.ts",
|
||||
"index.js",
|
||||
"package.json",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/webassembly-js-api": "0.0.1"
|
||||
}
|
||||
}
|
46
lib/loader/tests/assembly/index.ts
Normal file
46
lib/loader/tests/assembly/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import "allocator/arena";
|
||||
|
||||
export const COLOR: string = "red";
|
||||
|
||||
export function strlen(str: string): i32 {
|
||||
return str.length;
|
||||
}
|
||||
|
||||
export namespace math {
|
||||
export function add(a: i32, b: i32): i32 {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
export class Car {
|
||||
static readonly MAX_DOORS: i32 = 5;
|
||||
static usualDoors: i32 = 3;
|
||||
|
||||
numDoors: i32;
|
||||
private doorsOpen: bool = false;
|
||||
|
||||
get isDoorsOpen(): bool { return this.doorsOpen; }
|
||||
set isDoorsOpen(value: bool) { this.doorsOpen = value; }
|
||||
|
||||
constructor(numDoors: i32) {
|
||||
this.numDoors = numDoors;
|
||||
}
|
||||
|
||||
openDoors(): bool {
|
||||
if (this.doorsOpen) return false;
|
||||
this.doorsOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
closeDoors(): bool {
|
||||
if (!this.doorsOpen) return false;
|
||||
this.doorsOpen = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
free_memory(changetype<usize>(this));
|
||||
}
|
||||
}
|
||||
|
||||
export { allocate_memory, free_memory };
|
BIN
lib/loader/tests/build/untouched.wasm
Normal file
BIN
lib/loader/tests/build/untouched.wasm
Normal file
Binary file not shown.
34
lib/loader/tests/index.js
Normal file
34
lib/loader/tests/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
var fs = require("fs");
|
||||
var assert = require("assert");
|
||||
var inspect = require("util").inspect;
|
||||
|
||||
var loader = require("..");
|
||||
var buffer = fs.readFileSync(__dirname + "/build/untouched.wasm");
|
||||
var module = loader.instantiateBuffer(buffer, {});
|
||||
|
||||
console.log(inspect(module, true, 100, true));
|
||||
|
||||
// should inherit the usual utility
|
||||
var proto = Object.getPrototypeOf(module);
|
||||
assert(proto.I8 instanceof Int8Array);
|
||||
assert(proto.U8 instanceof Uint8Array);
|
||||
assert(proto.I16 instanceof Int16Array);
|
||||
assert(proto.U16 instanceof Uint16Array);
|
||||
assert(proto.I32 instanceof Int32Array);
|
||||
assert(proto.U32 instanceof Uint32Array);
|
||||
assert(proto.F32 instanceof Float32Array);
|
||||
assert(proto.F64 instanceof Float64Array);
|
||||
assert(typeof proto.newString === "function");
|
||||
assert(typeof proto.getString === "function");
|
||||
|
||||
// should export memory
|
||||
assert(module.memory instanceof WebAssembly.Memory);
|
||||
|
||||
// should be able to get an exported string
|
||||
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);
|
Reference in New Issue
Block a user