Skip to content

Refactor loader. Now instantiate methods return ResultObject + extended exports #1143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions lib/loader/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/// <reference lib="esnext.bigint" />

interface ResultObject {
module: WebAssembly.Module,
instance: WebAssembly.Instance
};

/** WebAssembly imports with two levels of nesting. */
interface ImportsObject extends Record<string, any> {
env?: {
Expand Down Expand Up @@ -92,13 +97,22 @@ interface ASUtil {
}

/** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */
export declare function instantiate<T extends {}>(source: WebAssembly.Module | BufferSource | Response | PromiseLike<WebAssembly.Module | BufferSource | Response>, imports?: ImportsObject): Promise<ASUtil & T>;
export declare function instantiate<T extends {}>(
source: WebAssembly.Module | BufferSource | Response | PromiseLike<WebAssembly.Module | BufferSource | Response>,
imports?: ImportsObject
): Promise<ResultObject & { exports: ASUtil & T }>;

/** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */
export declare function instantiateSync<T extends {}>(source: WebAssembly.Module | BufferSource, imports?: ImportsObject): ASUtil & T;
export declare function instantiateSync<T extends {}>(
source: WebAssembly.Module | BufferSource,
imports?: ImportsObject
): ResultObject & { exports: ASUtil & T };

/** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */
export declare function instantiateStreaming<T extends {}>(source: Response | PromiseLike<Response>, imports?: ImportsObject): Promise<ASUtil & T>;
export declare function instantiateStreaming<T extends {}>(
source: Response | PromiseLike<Response>,
imports?: ImportsObject
): Promise<ResultObject & { exports: ASUtil & T }>;

/** Demangles an AssemblyScript module's exports to a friendly object structure. */
export declare function demangle<T extends {}>(exports: {}, baseModule?: {}): T;
export declare function demangle<T extends {}>(exports: {}, extendedExports?: {}): T;
174 changes: 85 additions & 89 deletions lib/loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ const CHUNKSIZE = 1024;
function getStringImpl(buffer, ptr) {
const U32 = new Uint32Array(buffer);
const U16 = new Uint16Array(buffer);
var length = U32[(ptr + SIZE_OFFSET) >>> 2] >>> 1;
var offset = ptr >>> 1;
let length = U32[(ptr + SIZE_OFFSET) >>> 2] >>> 1;
let offset = ptr >>> 1;
if (length <= CHUNKSIZE) return String.fromCharCode.apply(String, U16.subarray(offset, offset + length));
const parts = [];
do {
Expand All @@ -58,7 +58,7 @@ function getStringImpl(buffer, ptr) {

/** Prepares the base module prior to instantiation. */
function preInstantiate(imports) {
const baseModule = {};
const extendedExports = {};

function getString(memory, ptr) {
if (!memory) return "<yet unknown>";
Expand All @@ -67,31 +67,31 @@ function preInstantiate(imports) {

// add common imports used by stdlib for convenience
const env = (imports.env = imports.env || {});
env.abort = env.abort || function abort(mesg, file, line, colm) {
const memory = baseModule.memory || env.memory; // prefer exported, otherwise try imported
throw Error("abort: " + getString(memory, mesg) + " in " + getString(memory, file) + "(" + line + ":" + colm + ")");
env.abort = env.abort || function abort(msg, file, line, colm) {
const memory = extendedExports.memory || env.memory; // prefer exported, otherwise try imported
throw Error("abort: " + getString(memory, msg) + " at " + getString(memory, file) + ":" + line + ":" + colm);
};
env.trace = env.trace || function trace(mesg, n) {
const memory = baseModule.memory || env.memory;
console.log("trace: " + getString(memory, mesg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", "));
env.trace = env.trace || function trace(msg, n) {
const memory = extendedExports.memory || env.memory;
console.log("trace: " + getString(memory, msg) + (n ? " " : "") + Array.prototype.slice.call(arguments, 2, 2 + n).join(", "));
};
env.seed = env.seed || function seed() {
return Date.now();
};
imports.Math = imports.Math || Math;
imports.Date = imports.Date || Date;

return baseModule;
return extendedExports;
}

/** Prepares the final module once instantiation is complete. */
function postInstantiate(baseModule, instance) {
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
function postInstantiate(extendedExports, instance) {
const exports = instance.exports;
const memory = exports.memory;
const table = exports.table;
const alloc = exports["__alloc"];
const retain = exports["__retain"];
const rttiBase = exports["__rtti_base"] || ~0; // oob if not present

/** Gets the runtime type info for the given id. */
function getInfo(id) {
Expand Down Expand Up @@ -128,7 +128,7 @@ function postInstantiate(baseModule, instance) {
return ptr;
}

baseModule.__allocString = __allocString;
extendedExports.__allocString = __allocString;

/** Reads a string from the module's memory by its pointer. */
function __getString(ptr) {
Expand All @@ -138,7 +138,7 @@ function postInstantiate(baseModule, instance) {
return getStringImpl(buffer, ptr);
}

baseModule.__getString = __getString;
extendedExports.__getString = __getString;

/** Gets the view matching the specified alignment, signedness and floatness. */
function getView(alignLog2, signed, float) {
Expand Down Expand Up @@ -181,7 +181,7 @@ function postInstantiate(baseModule, instance) {
return arr;
}

baseModule.__allocArray = __allocArray;
extendedExports.__allocArray = __allocArray;

/** Gets a live view on an array's values in the module's memory. Infers the array type from RTTI. */
function __getArrayView(arr) {
Expand All @@ -190,15 +190,15 @@ function postInstantiate(baseModule, instance) {
const info = getInfo(id);
if (!(info & ARRAYBUFFERVIEW)) throw Error("not an array: " + id);
const align = getValueAlign(info);
var buf = U32[arr + ARRAYBUFFERVIEW_DATASTART_OFFSET >>> 2];
let 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)
.subarray(buf >>>= align, buf + length);
}

baseModule.__getArrayView = __getArrayView;
extendedExports.__getArrayView = __getArrayView;

/** Copies an array's values from the module's memory. Infers the array type from RTTI. */
function __getArray(arr) {
Expand All @@ -209,7 +209,7 @@ function postInstantiate(baseModule, instance) {
return out;
}

baseModule.__getArray = __getArray;
extendedExports.__getArray = __getArray;

/** Copies an ArrayBuffer's value from the module's memory. */
function __getArrayBuffer(ptr) {
Expand All @@ -218,7 +218,7 @@ function postInstantiate(baseModule, instance) {
return buffer.slice(ptr, ptr + length);
}

baseModule.__getArrayBuffer = __getArrayBuffer;
extendedExports.__getArrayBuffer = __getArrayBuffer;

/** Copies a typed array's values from the module's memory. */
function getTypedArray(Type, alignLog2, ptr) {
Expand All @@ -233,89 +233,85 @@ function postInstantiate(baseModule, instance) {
return new Type(buffer, bufPtr, U32[bufPtr + SIZE_OFFSET >>> 2] >>> alignLog2);
}

baseModule.__getInt8Array = getTypedArray.bind(null, Int8Array, 0);
baseModule.__getInt8ArrayView = getTypedArrayView.bind(null, Int8Array, 0);
baseModule.__getUint8Array = getTypedArray.bind(null, Uint8Array, 0);
baseModule.__getUint8ArrayView = getTypedArrayView.bind(null, Uint8Array, 0);
baseModule.__getUint8ClampedArray = getTypedArray.bind(null, Uint8ClampedArray, 0);
baseModule.__getUint8ClampedArrayView = getTypedArrayView.bind(null, Uint8ClampedArray, 0);
baseModule.__getInt16Array = getTypedArray.bind(null, Int16Array, 1);
baseModule.__getInt16ArrayView = getTypedArrayView.bind(null, Int16Array, 1);
baseModule.__getUint16Array = getTypedArray.bind(null, Uint16Array, 1);
baseModule.__getUint16ArrayView = getTypedArrayView.bind(null, Uint16Array, 1);
baseModule.__getInt32Array = getTypedArray.bind(null, Int32Array, 2);
baseModule.__getInt32ArrayView = getTypedArrayView.bind(null, Int32Array, 2);
baseModule.__getUint32Array = getTypedArray.bind(null, Uint32Array, 2);
baseModule.__getUint32ArrayView = getTypedArrayView.bind(null, Uint32Array, 2);
extendedExports.__getInt8Array = getTypedArray.bind(null, Int8Array, 0);
extendedExports.__getInt8ArrayView = getTypedArrayView.bind(null, Int8Array, 0);
extendedExports.__getUint8Array = getTypedArray.bind(null, Uint8Array, 0);
extendedExports.__getUint8ArrayView = getTypedArrayView.bind(null, Uint8Array, 0);
extendedExports.__getUint8ClampedArray = getTypedArray.bind(null, Uint8ClampedArray, 0);
extendedExports.__getUint8ClampedArrayView = getTypedArrayView.bind(null, Uint8ClampedArray, 0);
extendedExports.__getInt16Array = getTypedArray.bind(null, Int16Array, 1);
extendedExports.__getInt16ArrayView = getTypedArrayView.bind(null, Int16Array, 1);
extendedExports.__getUint16Array = getTypedArray.bind(null, Uint16Array, 1);
extendedExports.__getUint16ArrayView = getTypedArrayView.bind(null, Uint16Array, 1);
extendedExports.__getInt32Array = getTypedArray.bind(null, Int32Array, 2);
extendedExports.__getInt32ArrayView = getTypedArrayView.bind(null, Int32Array, 2);
extendedExports.__getUint32Array = getTypedArray.bind(null, Uint32Array, 2);
extendedExports.__getUint32ArrayView = getTypedArrayView.bind(null, Uint32Array, 2);
if (BIGINT) {
baseModule.__getInt64Array = getTypedArray.bind(null, BigInt64Array, 3);
baseModule.__getInt64ArrayView = getTypedArrayView.bind(null, BigInt64Array, 3);
baseModule.__getUint64Array = getTypedArray.bind(null, BigUint64Array, 3);
baseModule.__getUint64ArrayView = getTypedArrayView.bind(null, BigUint64Array, 3);
extendedExports.__getInt64Array = getTypedArray.bind(null, BigInt64Array, 3);
extendedExports.__getInt64ArrayView = getTypedArrayView.bind(null, BigInt64Array, 3);
extendedExports.__getUint64Array = getTypedArray.bind(null, BigUint64Array, 3);
extendedExports.__getUint64ArrayView = getTypedArrayView.bind(null, BigUint64Array, 3);
}
baseModule.__getFloat32Array = getTypedArray.bind(null, Float32Array, 2);
baseModule.__getFloat32ArrayView = getTypedArrayView.bind(null, Float32Array, 2);
baseModule.__getFloat64Array = getTypedArray.bind(null, Float64Array, 3);
baseModule.__getFloat64ArrayView = getTypedArrayView.bind(null, Float64Array, 3);
extendedExports.__getFloat32Array = getTypedArray.bind(null, Float32Array, 2);
extendedExports.__getFloat32ArrayView = getTypedArrayView.bind(null, Float32Array, 2);
extendedExports.__getFloat64Array = getTypedArray.bind(null, Float64Array, 3);
extendedExports.__getFloat64ArrayView = getTypedArrayView.bind(null, Float64Array, 3);

/** Tests whether an object is an instance of the class represented by the specified base id. */
function __instanceof(ptr, baseId) {
const U32 = new Uint32Array(memory.buffer);
var id = U32[(ptr + ID_OFFSET) >>> 2];
let id = U32[(ptr + ID_OFFSET) >>> 2];
if (id <= U32[rttiBase >>> 2]) {
do if (id == baseId) return true;
while (id = getBase(id));
}
return false;
}

baseModule.__instanceof = __instanceof;
extendedExports.__instanceof = __instanceof;

// Pull basic exports to baseModule so code in preInstantiate can use them
baseModule.memory = baseModule.memory || memory;
baseModule.table = baseModule.table || table;
// Pull basic exports to extendedExports so code in preInstantiate can use them
extendedExports.memory = extendedExports.memory || memory;
extendedExports.table = extendedExports.table || table;

// Demangle exports and provide the usual utility on the prototype
return demangle(rawExports, baseModule);
return demangle(exports, extendedExports);
}

function isResponse(o) {
return typeof Response !== "undefined" && o instanceof Response;
function isResponse(src) {
return typeof Response !== "undefined" && src instanceof Response;
}

function isModule(src) {
return src instanceof WebAssembly.Module;
}

/** Asynchronously instantiates an AssemblyScript module from anything that can be instantiated. */
async function instantiate(source, imports) {
async function instantiate(source, imports = {}) {
if (isResponse(source = await source)) return instantiateStreaming(source, imports);
return postInstantiate(
preInstantiate(imports || (imports = {})),
await WebAssembly.instantiate(
source instanceof WebAssembly.Module
? source
: await WebAssembly.compile(source),
imports
)
);
const module = isModule(source) ? source : await WebAssembly.compile(source);
const extended = preInstantiate(imports);
const instance = await WebAssembly.instantiate(module, imports);
const exports = postInstantiate(extended, instance);
return { module, instance, exports };
}

exports.instantiate = instantiate;

/** Synchronously instantiates an AssemblyScript module from a WebAssembly.Module or binary buffer. */
function instantiateSync(source, imports) {
return postInstantiate(
preInstantiate(imports || (imports = {})),
new WebAssembly.Instance(
source instanceof WebAssembly.Module
? source
: new WebAssembly.Module(source),
imports
)
)
function instantiateSync(source, imports = {}) {
const module = isModule(source) ? source : new WebAssembly.Module(source);
const extended = preInstantiate(imports);
const instance = new WebAssembly.Instance(module, imports);
const exports = postInstantiate(extended, instance);
return { module, instance, exports };
}

exports.instantiateSync = instantiateSync;

/** Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by `fetch`. */
async function instantiateStreaming(source, imports) {
async function instantiateStreaming(source, imports = {}) {
if (!WebAssembly.instantiateStreaming) {
return instantiate(
isResponse(source = await source)
Expand All @@ -324,25 +320,25 @@ async function instantiateStreaming(source, imports) {
imports
);
}
return postInstantiate(
preInstantiate(imports || (imports = {})),
(await WebAssembly.instantiateStreaming(source, imports)).instance
);
const extended = preInstantiate(imports);
const result = await WebAssembly.instantiateStreaming(source, imports);
const exports = postInstantiate(extended, result.instance);
return { ...result, exports };
}

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 setArgumentsLength = exports["__argumentsLength"]
? function(length) { exports["__argumentsLength"].value = length; }
: exports["__setArgumentsLength"] || exports["__setargc"] || function() {};
function demangle(exports, extendedExports = {}) {
extendedExports = Object.create(extendedExports);
const setArgumentsLength = exports["__argumentsLength"]
? length => { exports["__argumentsLength"].value = length; }
: exports["__setArgumentsLength"] || exports["__setargc"] || (() => {});
for (let internalName in exports) {
if (!Object.prototype.hasOwnProperty.call(exports, internalName)) continue;
const elem = exports[internalName];
let parts = internalName.split(".");
let curr = module;
let curr = extendedExports;
while (parts.length > 1) {
let part = parts.shift();
if (!Object.prototype.hasOwnProperty.call(curr, part)) curr[part] = {};
Expand All @@ -351,10 +347,10 @@ function demangle(exports, baseModule) {
let name = parts[0];
let hash = name.indexOf("#");
if (hash >= 0) {
let className = name.substring(0, hash);
let classElem = curr[className];
const className = name.substring(0, hash);
const classElem = curr[className];
if (typeof classElem === "undefined" || !classElem.prototype) {
let ctor = function(...args) {
const ctor = function(...args) {
return ctor.wrap(ctor.prototype.constructor(0, ...args));
};
ctor.prototype = {
Expand Down Expand Up @@ -414,7 +410,7 @@ function demangle(exports, baseModule) {
}
}
}
return module;
return extendedExports;
}

exports.demangle = demangle;
Loading