893 lines
31 KiB
JavaScript
893 lines
31 KiB
JavaScript
const _WebAssembly = typeof WebAssembly !== 'undefined'
|
|
? WebAssembly
|
|
: typeof WXWebAssembly !== 'undefined'
|
|
? WXWebAssembly
|
|
: undefined;
|
|
const ENVIRONMENT_IS_NODE = typeof process === 'object' && process !== null &&
|
|
typeof process.versions === 'object' && process.versions !== null &&
|
|
typeof process.versions.node === 'string';
|
|
function getPostMessage(options) {
|
|
return typeof (options === null || options === void 0 ? void 0 : options.postMessage) === 'function'
|
|
? options.postMessage
|
|
: typeof postMessage === 'function'
|
|
? postMessage
|
|
: undefined;
|
|
}
|
|
function serizeErrorToBuffer(sab, code, error) {
|
|
const i32array = new Int32Array(sab);
|
|
Atomics.store(i32array, 0, code);
|
|
if (code > 1 && error) {
|
|
const name = error.name;
|
|
const message = error.message;
|
|
const stack = error.stack;
|
|
const nameBuffer = new TextEncoder().encode(name);
|
|
const messageBuffer = new TextEncoder().encode(message);
|
|
const stackBuffer = new TextEncoder().encode(stack);
|
|
Atomics.store(i32array, 1, nameBuffer.length);
|
|
Atomics.store(i32array, 2, messageBuffer.length);
|
|
Atomics.store(i32array, 3, stackBuffer.length);
|
|
const buffer = new Uint8Array(sab);
|
|
buffer.set(nameBuffer, 16);
|
|
buffer.set(messageBuffer, 16 + nameBuffer.length);
|
|
buffer.set(stackBuffer, 16 + nameBuffer.length + messageBuffer.length);
|
|
}
|
|
}
|
|
function deserizeErrorFromBuffer(sab) {
|
|
var _a, _b;
|
|
const i32array = new Int32Array(sab);
|
|
const status = Atomics.load(i32array, 0);
|
|
if (status <= 1) {
|
|
return null;
|
|
}
|
|
const nameLength = Atomics.load(i32array, 1);
|
|
const messageLength = Atomics.load(i32array, 2);
|
|
const stackLength = Atomics.load(i32array, 3);
|
|
const buffer = new Uint8Array(sab);
|
|
const nameBuffer = buffer.slice(16, 16 + nameLength);
|
|
const messageBuffer = buffer.slice(16 + nameLength, 16 + nameLength + messageLength);
|
|
const stackBuffer = buffer.slice(16 + nameLength + messageLength, 16 + nameLength + messageLength + stackLength);
|
|
const name = new TextDecoder().decode(nameBuffer);
|
|
const message = new TextDecoder().decode(messageBuffer);
|
|
const stack = new TextDecoder().decode(stackBuffer);
|
|
const ErrorConstructor = (_a = globalThis[name]) !== null && _a !== void 0 ? _a : (name === 'RuntimeError' ? ((_b = _WebAssembly.RuntimeError) !== null && _b !== void 0 ? _b : Error) : Error);
|
|
const error = new ErrorConstructor(message);
|
|
Object.defineProperty(error, 'stack', {
|
|
value: stack,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
return error;
|
|
}
|
|
function isSharedArrayBuffer(value) {
|
|
return ((typeof SharedArrayBuffer === 'function' && value instanceof SharedArrayBuffer) ||
|
|
(Object.prototype.toString.call(value) === '[object SharedArrayBuffer]'));
|
|
}
|
|
function isTrapError(e) {
|
|
try {
|
|
return e instanceof _WebAssembly.RuntimeError;
|
|
}
|
|
catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function createMessage(type, payload) {
|
|
return {
|
|
__emnapi__: {
|
|
type,
|
|
payload
|
|
}
|
|
};
|
|
}
|
|
|
|
const WASI_THREADS_MAX_TID = 0x1FFFFFFF;
|
|
function checkSharedWasmMemory(wasmMemory) {
|
|
if (wasmMemory) {
|
|
if (!isSharedArrayBuffer(wasmMemory.buffer)) {
|
|
throw new Error('Multithread features require shared wasm memory. ' +
|
|
'Try to compile with `-matomics -mbulk-memory` and use `--import-memory --shared-memory` during linking, ' +
|
|
'then create WebAssembly.Memory with `shared: true` option');
|
|
}
|
|
}
|
|
else {
|
|
if (typeof SharedArrayBuffer === 'undefined') {
|
|
throw new Error('Current environment does not support SharedArrayBuffer, threads are not available!');
|
|
}
|
|
}
|
|
}
|
|
function getReuseWorker(value) {
|
|
var _a;
|
|
if (typeof value === 'boolean') {
|
|
return value ? { size: 0, strict: false } : false;
|
|
}
|
|
if (typeof value === 'number') {
|
|
if (!(value >= 0)) {
|
|
throw new RangeError('reuseWorker: size must be a non-negative integer');
|
|
}
|
|
return { size: value, strict: false };
|
|
}
|
|
if (!value) {
|
|
return false;
|
|
}
|
|
const size = (_a = Number(value.size)) !== null && _a !== void 0 ? _a : 0;
|
|
const strict = Boolean(value.strict);
|
|
if (!(size > 0) && strict) {
|
|
throw new RangeError('reuseWorker: size must be set to positive integer if strict is set to true');
|
|
}
|
|
return { size, strict };
|
|
}
|
|
let nextWorkerID = 0;
|
|
class ThreadManager {
|
|
get nextWorkerID() { return nextWorkerID; }
|
|
constructor(options) {
|
|
var _a;
|
|
this.unusedWorkers = [];
|
|
this.runningWorkers = [];
|
|
this.pthreads = Object.create(null);
|
|
this.wasmModule = null;
|
|
this.wasmMemory = null;
|
|
this.messageEvents = new WeakMap();
|
|
if (!options) {
|
|
throw new TypeError('ThreadManager(): options is not provided');
|
|
}
|
|
if ('childThread' in options) {
|
|
this._childThread = Boolean(options.childThread);
|
|
}
|
|
else {
|
|
this._childThread = false;
|
|
}
|
|
if (this._childThread) {
|
|
this._onCreateWorker = undefined;
|
|
this._reuseWorker = false;
|
|
this._beforeLoad = undefined;
|
|
}
|
|
else {
|
|
this._onCreateWorker = options.onCreateWorker;
|
|
this._reuseWorker = getReuseWorker(options.reuseWorker);
|
|
this._beforeLoad = options.beforeLoad;
|
|
}
|
|
this.printErr = (_a = options.printErr) !== null && _a !== void 0 ? _a : console.error.bind(console);
|
|
}
|
|
init() {
|
|
if (!this._childThread) {
|
|
this.initMainThread();
|
|
}
|
|
}
|
|
initMainThread() {
|
|
this.preparePool();
|
|
}
|
|
preparePool() {
|
|
if (this._reuseWorker) {
|
|
if (this._reuseWorker.size) {
|
|
let pthreadPoolSize = this._reuseWorker.size;
|
|
while (pthreadPoolSize--) {
|
|
const worker = this.allocateUnusedWorker();
|
|
if (ENVIRONMENT_IS_NODE) {
|
|
worker.once('message', () => { });
|
|
worker.unref();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
shouldPreloadWorkers() {
|
|
return !this._childThread && this._reuseWorker && this._reuseWorker.size > 0;
|
|
}
|
|
loadWasmModuleToAllWorkers() {
|
|
const promises = Array(this.unusedWorkers.length);
|
|
for (let i = 0; i < this.unusedWorkers.length; ++i) {
|
|
const worker = this.unusedWorkers[i];
|
|
if (ENVIRONMENT_IS_NODE)
|
|
worker.ref();
|
|
promises[i] = this.loadWasmModuleToWorker(worker).then((w) => {
|
|
if (ENVIRONMENT_IS_NODE)
|
|
worker.unref();
|
|
return w;
|
|
}, (e) => {
|
|
if (ENVIRONMENT_IS_NODE)
|
|
worker.unref();
|
|
throw e;
|
|
});
|
|
}
|
|
return Promise.all(promises).catch((err) => {
|
|
this.terminateAllThreads();
|
|
throw err;
|
|
});
|
|
}
|
|
preloadWorkers() {
|
|
if (this.shouldPreloadWorkers()) {
|
|
return this.loadWasmModuleToAllWorkers();
|
|
}
|
|
return Promise.resolve([]);
|
|
}
|
|
setup(wasmModule, wasmMemory) {
|
|
this.wasmModule = wasmModule;
|
|
this.wasmMemory = wasmMemory;
|
|
}
|
|
markId(worker) {
|
|
if (worker.__emnapi_tid)
|
|
return worker.__emnapi_tid;
|
|
const tid = nextWorkerID + 43;
|
|
nextWorkerID = (nextWorkerID + 1) % (WASI_THREADS_MAX_TID - 42);
|
|
this.pthreads[tid] = worker;
|
|
worker.__emnapi_tid = tid;
|
|
return tid;
|
|
}
|
|
returnWorkerToPool(worker) {
|
|
var tid = worker.__emnapi_tid;
|
|
if (tid !== undefined) {
|
|
delete this.pthreads[tid];
|
|
}
|
|
this.unusedWorkers.push(worker);
|
|
this.runningWorkers.splice(this.runningWorkers.indexOf(worker), 1);
|
|
delete worker.__emnapi_tid;
|
|
if (ENVIRONMENT_IS_NODE) {
|
|
worker.unref();
|
|
}
|
|
}
|
|
loadWasmModuleToWorker(worker, sab) {
|
|
if (worker.whenLoaded)
|
|
return worker.whenLoaded;
|
|
const err = this.printErr;
|
|
const beforeLoad = this._beforeLoad;
|
|
const _this = this;
|
|
worker.whenLoaded = new Promise((resolve, reject) => {
|
|
const handleError = function (e) {
|
|
let message = 'worker sent an error!';
|
|
if (worker.__emnapi_tid !== undefined) {
|
|
message = 'worker (tid = ' + worker.__emnapi_tid + ') sent an error!';
|
|
}
|
|
if ('message' in e) {
|
|
err(message + ' ' + e.message);
|
|
if (e.message.indexOf('RuntimeError') !== -1 || e.message.indexOf('unreachable') !== -1) {
|
|
try {
|
|
_this.terminateAllThreads();
|
|
}
|
|
catch (_) { }
|
|
}
|
|
}
|
|
else {
|
|
err(message);
|
|
}
|
|
reject(e);
|
|
throw e;
|
|
};
|
|
const handleMessage = (data) => {
|
|
if (data.__emnapi__) {
|
|
const type = data.__emnapi__.type;
|
|
const payload = data.__emnapi__.payload;
|
|
if (type === 'loaded') {
|
|
worker.loaded = true;
|
|
if (ENVIRONMENT_IS_NODE && !worker.__emnapi_tid) {
|
|
worker.unref();
|
|
}
|
|
resolve(worker);
|
|
}
|
|
else if (type === 'cleanup-thread') {
|
|
if (payload.tid in this.pthreads) {
|
|
this.cleanThread(worker, payload.tid);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
worker.onmessage = (e) => {
|
|
handleMessage(e.data);
|
|
this.fireMessageEvent(worker, e);
|
|
};
|
|
worker.onerror = handleError;
|
|
if (ENVIRONMENT_IS_NODE) {
|
|
worker.on('message', function (data) {
|
|
var _a, _b;
|
|
(_b = (_a = worker).onmessage) === null || _b === void 0 ? void 0 : _b.call(_a, {
|
|
data
|
|
});
|
|
});
|
|
worker.on('error', function (e) {
|
|
var _a, _b;
|
|
(_b = (_a = worker).onerror) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
});
|
|
worker.on('detachedExit', function () { });
|
|
}
|
|
if (typeof beforeLoad === 'function') {
|
|
beforeLoad(worker);
|
|
}
|
|
try {
|
|
worker.postMessage(createMessage('load', {
|
|
wasmModule: this.wasmModule,
|
|
wasmMemory: this.wasmMemory,
|
|
sab
|
|
}));
|
|
}
|
|
catch (err) {
|
|
checkSharedWasmMemory(this.wasmMemory);
|
|
throw err;
|
|
}
|
|
});
|
|
return worker.whenLoaded;
|
|
}
|
|
allocateUnusedWorker() {
|
|
const _onCreateWorker = this._onCreateWorker;
|
|
if (typeof _onCreateWorker !== 'function') {
|
|
throw new TypeError('`options.onCreateWorker` is not provided');
|
|
}
|
|
const worker = _onCreateWorker({ type: 'thread', name: 'emnapi-pthread' });
|
|
this.unusedWorkers.push(worker);
|
|
return worker;
|
|
}
|
|
getNewWorker(sab) {
|
|
if (this._reuseWorker) {
|
|
if (this.unusedWorkers.length === 0) {
|
|
if (this._reuseWorker.strict) {
|
|
if (!ENVIRONMENT_IS_NODE) {
|
|
const err = this.printErr;
|
|
err('Tried to spawn a new thread, but the thread pool is exhausted.\n' +
|
|
'This might result in a deadlock unless some threads eventually exit or the code explicitly breaks out to the event loop.');
|
|
return;
|
|
}
|
|
}
|
|
const worker = this.allocateUnusedWorker();
|
|
this.loadWasmModuleToWorker(worker, sab);
|
|
}
|
|
return this.unusedWorkers.pop();
|
|
}
|
|
const worker = this.allocateUnusedWorker();
|
|
this.loadWasmModuleToWorker(worker, sab);
|
|
return this.unusedWorkers.pop();
|
|
}
|
|
cleanThread(worker, tid, force) {
|
|
if (!force && this._reuseWorker) {
|
|
this.returnWorkerToPool(worker);
|
|
}
|
|
else {
|
|
delete this.pthreads[tid];
|
|
const index = this.runningWorkers.indexOf(worker);
|
|
if (index !== -1) {
|
|
this.runningWorkers.splice(index, 1);
|
|
}
|
|
this.terminateWorker(worker);
|
|
delete worker.__emnapi_tid;
|
|
}
|
|
}
|
|
terminateWorker(worker) {
|
|
var _a;
|
|
const tid = worker.__emnapi_tid;
|
|
worker.terminate();
|
|
(_a = this.messageEvents.get(worker)) === null || _a === void 0 ? void 0 : _a.clear();
|
|
this.messageEvents.delete(worker);
|
|
worker.onmessage = (e) => {
|
|
if (e.data.__emnapi__) {
|
|
const err = this.printErr;
|
|
err('received "' + e.data.__emnapi__.type + '" command from terminated worker: ' + tid);
|
|
}
|
|
};
|
|
}
|
|
terminateAllThreads() {
|
|
for (let i = 0; i < this.runningWorkers.length; ++i) {
|
|
this.terminateWorker(this.runningWorkers[i]);
|
|
}
|
|
for (let i = 0; i < this.unusedWorkers.length; ++i) {
|
|
this.terminateWorker(this.unusedWorkers[i]);
|
|
}
|
|
this.unusedWorkers = [];
|
|
this.runningWorkers = [];
|
|
this.pthreads = Object.create(null);
|
|
this.preparePool();
|
|
}
|
|
addMessageEventListener(worker, onMessage) {
|
|
let listeners = this.messageEvents.get(worker);
|
|
if (!listeners) {
|
|
listeners = new Set();
|
|
this.messageEvents.set(worker, listeners);
|
|
}
|
|
listeners.add(onMessage);
|
|
return () => {
|
|
listeners === null || listeners === void 0 ? void 0 : listeners.delete(onMessage);
|
|
};
|
|
}
|
|
fireMessageEvent(worker, e) {
|
|
const listeners = this.messageEvents.get(worker);
|
|
if (!listeners)
|
|
return;
|
|
const err = this.printErr;
|
|
listeners.forEach((listener) => {
|
|
try {
|
|
listener(e);
|
|
}
|
|
catch (e) {
|
|
err(e.stack);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
const kIsProxy = Symbol('kIsProxy');
|
|
function createInstanceProxy(instance, memory) {
|
|
if (instance[kIsProxy])
|
|
return instance;
|
|
const originalExports = instance.exports;
|
|
const createHandler = function (target) {
|
|
const handlers = [
|
|
'apply',
|
|
'construct',
|
|
'defineProperty',
|
|
'deleteProperty',
|
|
'get',
|
|
'getOwnPropertyDescriptor',
|
|
'getPrototypeOf',
|
|
'has',
|
|
'isExtensible',
|
|
'ownKeys',
|
|
'preventExtensions',
|
|
'set',
|
|
'setPrototypeOf'
|
|
];
|
|
const handler = {};
|
|
for (let i = 0; i < handlers.length; i++) {
|
|
const name = handlers[i];
|
|
handler[name] = function () {
|
|
const args = Array.prototype.slice.call(arguments, 1);
|
|
args.unshift(target);
|
|
return Reflect[name].apply(Reflect, args);
|
|
};
|
|
}
|
|
return handler;
|
|
};
|
|
const handler = createHandler(originalExports);
|
|
const _initialize = () => { };
|
|
const _start = () => 0;
|
|
handler.get = function (_target, p, receiver) {
|
|
var _a;
|
|
if (p === 'memory') {
|
|
return (_a = (typeof memory === 'function' ? memory() : memory)) !== null && _a !== void 0 ? _a : Reflect.get(originalExports, p, receiver);
|
|
}
|
|
if (p === '_initialize') {
|
|
return p in originalExports ? _initialize : undefined;
|
|
}
|
|
if (p === '_start') {
|
|
return p in originalExports ? _start : undefined;
|
|
}
|
|
return Reflect.get(originalExports, p, receiver);
|
|
};
|
|
handler.has = function (_target, p) {
|
|
if (p === 'memory')
|
|
return true;
|
|
return Reflect.has(originalExports, p);
|
|
};
|
|
const exportsProxy = new Proxy(Object.create(null), handler);
|
|
return new Proxy(instance, {
|
|
get(target, p, receiver) {
|
|
if (p === 'exports') {
|
|
return exportsProxy;
|
|
}
|
|
if (p === kIsProxy) {
|
|
return true;
|
|
}
|
|
return Reflect.get(target, p, receiver);
|
|
}
|
|
});
|
|
}
|
|
|
|
const patchedWasiInstances = new WeakMap();
|
|
class WASIThreads {
|
|
constructor(options) {
|
|
if (!options) {
|
|
throw new TypeError('WASIThreads(): options is not provided');
|
|
}
|
|
if (!options.wasi) {
|
|
throw new TypeError('WASIThreads(): options.wasi is not provided');
|
|
}
|
|
patchedWasiInstances.set(this, new WeakSet());
|
|
const wasi = options.wasi;
|
|
patchWasiInstance(this, wasi);
|
|
this.wasi = wasi;
|
|
if ('childThread' in options) {
|
|
this.childThread = Boolean(options.childThread);
|
|
}
|
|
else {
|
|
this.childThread = false;
|
|
}
|
|
this.PThread = undefined;
|
|
if ('threadManager' in options) {
|
|
if (typeof options.threadManager === 'function') {
|
|
this.PThread = options.threadManager();
|
|
}
|
|
else {
|
|
this.PThread = options.threadManager;
|
|
}
|
|
}
|
|
else {
|
|
if (!this.childThread) {
|
|
this.PThread = new ThreadManager(options);
|
|
this.PThread.init();
|
|
}
|
|
}
|
|
let waitThreadStart = false;
|
|
if ('waitThreadStart' in options) {
|
|
waitThreadStart = typeof options.waitThreadStart === 'number' ? options.waitThreadStart : Boolean(options.waitThreadStart);
|
|
}
|
|
const postMessage = getPostMessage(options);
|
|
if (this.childThread && typeof postMessage !== 'function') {
|
|
throw new TypeError('options.postMessage is not a function');
|
|
}
|
|
this.postMessage = postMessage;
|
|
const wasm64 = Boolean(options.wasm64);
|
|
const onMessage = (e) => {
|
|
if (e.data.__emnapi__) {
|
|
const type = e.data.__emnapi__.type;
|
|
const payload = e.data.__emnapi__.payload;
|
|
if (type === 'spawn-thread') {
|
|
threadSpawn(payload.startArg, payload.errorOrTid);
|
|
}
|
|
else if (type === 'terminate-all-threads') {
|
|
this.terminateAllThreads();
|
|
}
|
|
}
|
|
};
|
|
const threadSpawn = (startArg, errorOrTid) => {
|
|
var _a;
|
|
const EAGAIN = 6;
|
|
const isNewABI = errorOrTid !== undefined;
|
|
try {
|
|
checkSharedWasmMemory(this.wasmMemory);
|
|
}
|
|
catch (err) {
|
|
(_a = this.PThread) === null || _a === void 0 ? void 0 : _a.printErr(err.stack);
|
|
if (isNewABI) {
|
|
const struct = new Int32Array(this.wasmMemory.buffer, errorOrTid, 2);
|
|
Atomics.store(struct, 0, 1);
|
|
Atomics.store(struct, 1, EAGAIN);
|
|
Atomics.notify(struct, 1);
|
|
return 1;
|
|
}
|
|
else {
|
|
return -EAGAIN;
|
|
}
|
|
}
|
|
if (!isNewABI) {
|
|
const malloc = this.wasmInstance.exports.malloc;
|
|
errorOrTid = wasm64 ? Number(malloc(BigInt(8))) : malloc(8);
|
|
if (!errorOrTid) {
|
|
return -48;
|
|
}
|
|
}
|
|
const _free = this.wasmInstance.exports.free;
|
|
const free = wasm64 ? (ptr) => { _free(BigInt(ptr)); } : _free;
|
|
const struct = new Int32Array(this.wasmMemory.buffer, errorOrTid, 2);
|
|
Atomics.store(struct, 0, 0);
|
|
Atomics.store(struct, 1, 0);
|
|
if (this.childThread) {
|
|
postMessage(createMessage('spawn-thread', {
|
|
startArg,
|
|
errorOrTid: errorOrTid
|
|
}));
|
|
Atomics.wait(struct, 1, 0);
|
|
const isError = Atomics.load(struct, 0);
|
|
const result = Atomics.load(struct, 1);
|
|
if (isNewABI) {
|
|
return isError;
|
|
}
|
|
free(errorOrTid);
|
|
return isError ? -result : result;
|
|
}
|
|
const shouldWait = waitThreadStart || (waitThreadStart === 0);
|
|
let sab;
|
|
if (shouldWait) {
|
|
sab = new Int32Array(new SharedArrayBuffer(16 + 8192));
|
|
Atomics.store(sab, 0, 0);
|
|
}
|
|
let worker;
|
|
let tid;
|
|
const PThread = this.PThread;
|
|
try {
|
|
worker = PThread.getNewWorker(sab);
|
|
if (!worker) {
|
|
throw new Error('failed to get new worker');
|
|
}
|
|
PThread.addMessageEventListener(worker, onMessage);
|
|
tid = PThread.markId(worker);
|
|
if (ENVIRONMENT_IS_NODE) {
|
|
worker.ref();
|
|
}
|
|
worker.postMessage(createMessage('start', {
|
|
tid,
|
|
arg: startArg,
|
|
sab
|
|
}));
|
|
if (shouldWait) {
|
|
if (typeof waitThreadStart === 'number') {
|
|
const waitResult = Atomics.wait(sab, 0, 0, waitThreadStart);
|
|
if (waitResult === 'timed-out') {
|
|
try {
|
|
PThread.cleanThread(worker, tid, true);
|
|
}
|
|
catch (_) { }
|
|
throw new Error('Spawning thread timed out. Please check if the worker is created successfully and if message is handled properly in the worker.');
|
|
}
|
|
}
|
|
else {
|
|
Atomics.wait(sab, 0, 0);
|
|
}
|
|
const r = Atomics.load(sab, 0);
|
|
if (r > 1) {
|
|
try {
|
|
PThread.cleanThread(worker, tid, true);
|
|
}
|
|
catch (_) { }
|
|
throw deserizeErrorFromBuffer(sab.buffer);
|
|
}
|
|
}
|
|
}
|
|
catch (e) {
|
|
Atomics.store(struct, 0, 1);
|
|
Atomics.store(struct, 1, EAGAIN);
|
|
Atomics.notify(struct, 1);
|
|
PThread === null || PThread === void 0 ? void 0 : PThread.printErr(e.stack);
|
|
if (isNewABI) {
|
|
return 1;
|
|
}
|
|
free(errorOrTid);
|
|
return -EAGAIN;
|
|
}
|
|
Atomics.store(struct, 0, 0);
|
|
Atomics.store(struct, 1, tid);
|
|
Atomics.notify(struct, 1);
|
|
PThread.runningWorkers.push(worker);
|
|
if (!shouldWait) {
|
|
worker.whenLoaded.catch((err) => {
|
|
delete worker.whenLoaded;
|
|
PThread.cleanThread(worker, tid, true);
|
|
throw err;
|
|
});
|
|
}
|
|
if (isNewABI) {
|
|
return 0;
|
|
}
|
|
free(errorOrTid);
|
|
return tid;
|
|
};
|
|
this.threadSpawn = threadSpawn;
|
|
}
|
|
getImportObject() {
|
|
return {
|
|
wasi: {
|
|
'thread-spawn': this.threadSpawn
|
|
}
|
|
};
|
|
}
|
|
setup(wasmInstance, wasmModule, wasmMemory) {
|
|
wasmMemory !== null && wasmMemory !== void 0 ? wasmMemory : (wasmMemory = wasmInstance.exports.memory);
|
|
this.wasmInstance = wasmInstance;
|
|
this.wasmMemory = wasmMemory;
|
|
if (this.PThread) {
|
|
this.PThread.setup(wasmModule, wasmMemory);
|
|
}
|
|
}
|
|
preloadWorkers() {
|
|
if (this.PThread) {
|
|
return this.PThread.preloadWorkers();
|
|
}
|
|
return Promise.resolve([]);
|
|
}
|
|
initialize(instance, module, memory) {
|
|
const exports = instance.exports;
|
|
memory !== null && memory !== void 0 ? memory : (memory = exports.memory);
|
|
if (this.childThread) {
|
|
instance = createInstanceProxy(instance, memory);
|
|
}
|
|
this.setup(instance, module, memory);
|
|
const wasi = this.wasi;
|
|
if (('_start' in exports) && (typeof exports._start === 'function')) {
|
|
if (this.childThread) {
|
|
wasi.start(instance);
|
|
try {
|
|
const kStarted = getWasiSymbol(wasi, 'kStarted');
|
|
wasi[kStarted] = false;
|
|
}
|
|
catch (_) { }
|
|
}
|
|
else {
|
|
setupInstance(wasi, instance);
|
|
}
|
|
}
|
|
else {
|
|
wasi.initialize(instance);
|
|
}
|
|
return instance;
|
|
}
|
|
start(instance, module, memory) {
|
|
const exports = instance.exports;
|
|
memory !== null && memory !== void 0 ? memory : (memory = exports.memory);
|
|
if (this.childThread) {
|
|
instance = createInstanceProxy(instance, memory);
|
|
}
|
|
this.setup(instance, module, memory);
|
|
const exitCode = this.wasi.start(instance);
|
|
return { exitCode, instance };
|
|
}
|
|
terminateAllThreads() {
|
|
var _a;
|
|
if (!this.childThread) {
|
|
(_a = this.PThread) === null || _a === void 0 ? void 0 : _a.terminateAllThreads();
|
|
}
|
|
else {
|
|
this.postMessage(createMessage('terminate-all-threads', {}));
|
|
}
|
|
}
|
|
}
|
|
function patchWasiInstance(wasiThreads, wasi) {
|
|
const patched = patchedWasiInstances.get(wasiThreads);
|
|
if (patched.has(wasi)) {
|
|
return;
|
|
}
|
|
const _this = wasiThreads;
|
|
const wasiImport = wasi.wasiImport;
|
|
if (wasiImport) {
|
|
const proc_exit = wasiImport.proc_exit;
|
|
wasiImport.proc_exit = function (code) {
|
|
_this.terminateAllThreads();
|
|
return proc_exit.call(this, code);
|
|
};
|
|
}
|
|
if (!_this.childThread) {
|
|
const start = wasi.start;
|
|
if (typeof start === 'function') {
|
|
wasi.start = function (instance) {
|
|
try {
|
|
return start.call(this, instance);
|
|
}
|
|
catch (err) {
|
|
if (isTrapError(err)) {
|
|
_this.terminateAllThreads();
|
|
}
|
|
throw err;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
patched.add(wasi);
|
|
}
|
|
function getWasiSymbol(wasi, description) {
|
|
const symbols = Object.getOwnPropertySymbols(wasi);
|
|
const selectDescription = (description) => (s) => {
|
|
if (s.description) {
|
|
return s.description === description;
|
|
}
|
|
return s.toString() === `Symbol(${description})`;
|
|
};
|
|
if (Array.isArray(description)) {
|
|
return description.map(d => symbols.filter(selectDescription(d))[0]);
|
|
}
|
|
return symbols.filter(selectDescription(description))[0];
|
|
}
|
|
function setupInstance(wasi, instance) {
|
|
const [kInstance, kSetMemory] = getWasiSymbol(wasi, ['kInstance', 'kSetMemory']);
|
|
wasi[kInstance] = instance;
|
|
wasi[kSetMemory](instance.exports.memory);
|
|
}
|
|
|
|
class ThreadMessageHandler {
|
|
constructor(options) {
|
|
const postMsg = getPostMessage(options);
|
|
if (typeof postMsg !== 'function') {
|
|
throw new TypeError('options.postMessage is not a function');
|
|
}
|
|
this.postMessage = postMsg;
|
|
this.onLoad = options === null || options === void 0 ? void 0 : options.onLoad;
|
|
this.onError = typeof (options === null || options === void 0 ? void 0 : options.onError) === 'function' ? options.onError : (_type, err) => { throw err; };
|
|
this.instance = undefined;
|
|
this.messagesBeforeLoad = [];
|
|
}
|
|
instantiate(data) {
|
|
if (typeof this.onLoad === 'function') {
|
|
return this.onLoad(data);
|
|
}
|
|
throw new Error('ThreadMessageHandler.prototype.instantiate is not implemented');
|
|
}
|
|
handle(e) {
|
|
var _a;
|
|
if ((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.__emnapi__) {
|
|
const type = e.data.__emnapi__.type;
|
|
const payload = e.data.__emnapi__.payload;
|
|
try {
|
|
if (type === 'load') {
|
|
this._load(payload);
|
|
}
|
|
else if (type === 'start') {
|
|
this.handleAfterLoad(e, () => {
|
|
this._start(payload);
|
|
});
|
|
}
|
|
}
|
|
catch (err) {
|
|
this.onError(err, type);
|
|
}
|
|
}
|
|
}
|
|
_load(payload) {
|
|
if (this.instance !== undefined)
|
|
return;
|
|
let source;
|
|
try {
|
|
source = this.instantiate(payload);
|
|
}
|
|
catch (err) {
|
|
this._loaded(err, null, payload);
|
|
return;
|
|
}
|
|
const then = source && 'then' in source ? source.then : undefined;
|
|
if (typeof then === 'function') {
|
|
then.call(source, (source) => { this._loaded(null, source, payload); }, (err) => { this._loaded(err, null, payload); });
|
|
}
|
|
else {
|
|
this._loaded(null, source, payload);
|
|
}
|
|
}
|
|
_start(payload) {
|
|
const wasi_thread_start = this.instance.exports.wasi_thread_start;
|
|
if (typeof wasi_thread_start !== 'function') {
|
|
const err = new TypeError('wasi_thread_start is not exported');
|
|
notifyPthreadCreateResult(payload.sab, 2, err);
|
|
throw err;
|
|
}
|
|
const postMessage = this.postMessage;
|
|
const tid = payload.tid;
|
|
const startArg = payload.arg;
|
|
notifyPthreadCreateResult(payload.sab, 1);
|
|
try {
|
|
wasi_thread_start(tid, startArg);
|
|
}
|
|
catch (err) {
|
|
if (err !== 'unwind') {
|
|
throw err;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
postMessage(createMessage('cleanup-thread', { tid }));
|
|
}
|
|
_loaded(err, source, payload) {
|
|
if (err) {
|
|
notifyPthreadCreateResult(payload.sab, 2, err);
|
|
throw err;
|
|
}
|
|
if (source == null) {
|
|
const err = new TypeError('onLoad should return an object');
|
|
notifyPthreadCreateResult(payload.sab, 2, err);
|
|
throw err;
|
|
}
|
|
const instance = source.instance;
|
|
if (!instance) {
|
|
const err = new TypeError('onLoad should return an object which includes "instance"');
|
|
notifyPthreadCreateResult(payload.sab, 2, err);
|
|
throw err;
|
|
}
|
|
this.instance = instance;
|
|
const postMessage = this.postMessage;
|
|
postMessage(createMessage('loaded', {}));
|
|
const messages = this.messagesBeforeLoad;
|
|
this.messagesBeforeLoad = [];
|
|
for (let i = 0; i < messages.length; i++) {
|
|
const data = messages[i];
|
|
this.handle({ data });
|
|
}
|
|
}
|
|
handleAfterLoad(e, f) {
|
|
if (this.instance !== undefined) {
|
|
f.call(this, e);
|
|
}
|
|
else {
|
|
this.messagesBeforeLoad.push(e.data);
|
|
}
|
|
}
|
|
}
|
|
function notifyPthreadCreateResult(sab, result, error) {
|
|
if (sab) {
|
|
serizeErrorToBuffer(sab.buffer, result, error);
|
|
Atomics.notify(sab, 0);
|
|
}
|
|
}
|
|
|
|
export { ThreadManager, ThreadMessageHandler, WASIThreads, createInstanceProxy, isSharedArrayBuffer, isTrapError };
|