154 lines
5.7 KiB
JavaScript
154 lines
5.7 KiB
JavaScript
import { _WebAssembly } from "./webassembly.mjs";
|
|
import { isPromiseLike, wrapInstanceExports } from "./wasi/util.mjs";
|
|
const ignoreNames = [
|
|
'asyncify_get_state',
|
|
'asyncify_start_rewind',
|
|
'asyncify_start_unwind',
|
|
'asyncify_stop_rewind',
|
|
'asyncify_stop_unwind'
|
|
];
|
|
// const wrappedExports = new WeakMap<WebAssembly.Exports, WebAssembly.Exports>()
|
|
var AsyncifyState;
|
|
(function (AsyncifyState) {
|
|
AsyncifyState[AsyncifyState["NONE"] = 0] = "NONE";
|
|
AsyncifyState[AsyncifyState["UNWINDING"] = 1] = "UNWINDING";
|
|
AsyncifyState[AsyncifyState["REWINDING"] = 2] = "REWINDING";
|
|
})(AsyncifyState || (AsyncifyState = {}));
|
|
function tryAllocate(instance, wasm64, size, mallocName) {
|
|
if (typeof instance.exports[mallocName] !== 'function' || size <= 0) {
|
|
return {
|
|
wasm64,
|
|
dataPtr: 16,
|
|
start: wasm64 ? 32 : 24,
|
|
end: 1024
|
|
};
|
|
}
|
|
const malloc = instance.exports[mallocName];
|
|
const dataPtr = wasm64 ? Number(malloc(BigInt(16) + BigInt(size))) : malloc(8 + size);
|
|
if (dataPtr === 0) {
|
|
throw new Error('Allocate asyncify data failed');
|
|
}
|
|
return wasm64
|
|
? { wasm64, dataPtr, start: dataPtr + 16, end: dataPtr + 16 + size }
|
|
: { wasm64, dataPtr, start: dataPtr + 8, end: dataPtr + 8 + size };
|
|
}
|
|
/** @public */
|
|
export class Asyncify {
|
|
constructor() {
|
|
this.value = undefined;
|
|
this.exports = undefined;
|
|
this.dataPtr = 0;
|
|
}
|
|
init(memory, instance, options) {
|
|
var _a, _b;
|
|
if (this.exports) {
|
|
throw new Error('Asyncify has been initialized');
|
|
}
|
|
if (!(memory instanceof _WebAssembly.Memory)) {
|
|
throw new TypeError('Require WebAssembly.Memory object');
|
|
}
|
|
const exports = instance.exports;
|
|
for (let i = 0; i < ignoreNames.length; ++i) {
|
|
if (typeof exports[ignoreNames[i]] !== 'function') {
|
|
throw new TypeError('Invalid asyncify wasm');
|
|
}
|
|
}
|
|
let address;
|
|
const wasm64 = Boolean(options.wasm64);
|
|
if (!options.tryAllocate) {
|
|
address = {
|
|
wasm64,
|
|
dataPtr: 16,
|
|
start: wasm64 ? 32 : 24,
|
|
end: 1024
|
|
};
|
|
}
|
|
else {
|
|
if (options.tryAllocate === true) {
|
|
address = tryAllocate(instance, wasm64, 4096, 'malloc');
|
|
}
|
|
else {
|
|
address = tryAllocate(instance, wasm64, (_a = options.tryAllocate.size) !== null && _a !== void 0 ? _a : 4096, (_b = options.tryAllocate.name) !== null && _b !== void 0 ? _b : 'malloc');
|
|
}
|
|
}
|
|
this.dataPtr = address.dataPtr;
|
|
if (wasm64) {
|
|
new BigInt64Array(memory.buffer, this.dataPtr).set([BigInt(address.start), BigInt(address.end)]);
|
|
}
|
|
else {
|
|
new Int32Array(memory.buffer, this.dataPtr).set([address.start, address.end]);
|
|
}
|
|
this.exports = this.wrapExports(exports, options.wrapExports);
|
|
const asyncifiedInstance = Object.create(_WebAssembly.Instance.prototype);
|
|
Object.defineProperty(asyncifiedInstance, 'exports', { value: this.exports });
|
|
// Object.setPrototypeOf(instance, Instance.prototype)
|
|
return asyncifiedInstance;
|
|
}
|
|
assertState() {
|
|
if (this.exports.asyncify_get_state() !== AsyncifyState.NONE) {
|
|
throw new Error('Asyncify state error');
|
|
}
|
|
}
|
|
wrapImportFunction(f) {
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
const _this = this;
|
|
return (function () {
|
|
// eslint-disable-next-line no-unreachable-loop
|
|
while (_this.exports.asyncify_get_state() === AsyncifyState.REWINDING) {
|
|
_this.exports.asyncify_stop_rewind();
|
|
return _this.value;
|
|
}
|
|
_this.assertState();
|
|
const v = f.apply(this, arguments);
|
|
if (!isPromiseLike(v))
|
|
return v;
|
|
_this.exports.asyncify_start_unwind(_this.dataPtr);
|
|
_this.value = v;
|
|
});
|
|
}
|
|
wrapImports(imports) {
|
|
const importObject = {};
|
|
Object.keys(imports).forEach(k => {
|
|
const mod = imports[k];
|
|
const newModule = {};
|
|
Object.keys(mod).forEach(name => {
|
|
const importValue = mod[name];
|
|
if (typeof importValue === 'function') {
|
|
newModule[name] = this.wrapImportFunction(importValue);
|
|
}
|
|
else {
|
|
newModule[name] = importValue;
|
|
}
|
|
});
|
|
importObject[k] = newModule;
|
|
});
|
|
return importObject;
|
|
}
|
|
wrapExportFunction(f) {
|
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
const _this = this;
|
|
return (async function () {
|
|
_this.assertState();
|
|
let ret = f.apply(this, arguments);
|
|
while (_this.exports.asyncify_get_state() === AsyncifyState.UNWINDING) {
|
|
_this.exports.asyncify_stop_unwind();
|
|
_this.value = await _this.value;
|
|
_this.assertState();
|
|
_this.exports.asyncify_start_rewind(_this.dataPtr);
|
|
ret = f.call(this);
|
|
}
|
|
_this.assertState();
|
|
return ret;
|
|
});
|
|
}
|
|
wrapExports(exports, needWrap) {
|
|
return wrapInstanceExports(exports, (exportValue, name) => {
|
|
let ignore = ignoreNames.indexOf(name) !== -1 || typeof exportValue !== 'function';
|
|
if (Array.isArray(needWrap)) {
|
|
ignore = ignore || (needWrap.indexOf(name) === -1);
|
|
}
|
|
return ignore ? exportValue : this.wrapExportFunction(exportValue);
|
|
});
|
|
}
|
|
}
|