mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00

PR-URL: https://github.com/nodejs/node/pull/57214 Reviewed-By: Guy Bedford <guybedford@gmail.com>
230 lines
7 KiB
JavaScript
230 lines
7 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const fixtures = require('../common/fixtures');
|
|
const tmpdir = require('../common/tmpdir');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { parseArgs } = require('util');
|
|
const common = require('../common');
|
|
const { WASI } = require('wasi');
|
|
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
|
|
|
|
const args = parseArgs({
|
|
allowPositionals: true,
|
|
options: {
|
|
target: {
|
|
type: 'string',
|
|
default: 'wasm32-wasip1',
|
|
},
|
|
},
|
|
strict: false,
|
|
});
|
|
|
|
function returnOnExitEnvToValue(env) {
|
|
const envValue = env.RETURN_ON_EXIT;
|
|
if (envValue === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return envValue === 'true';
|
|
}
|
|
|
|
common.expectWarning('ExperimentalWarning',
|
|
'WASI is an experimental feature and might change at any time');
|
|
|
|
tmpdir.refresh();
|
|
const wasmDir = path.join(__dirname, '..', 'wasi', 'wasm');
|
|
const wasiPreview1 = new WASI({
|
|
version: 'preview1',
|
|
args: ['foo', '-bar', '--baz=value'],
|
|
env: process.env,
|
|
preopens: {
|
|
'/sandbox': fixtures.path('wasi'),
|
|
'/tmp': tmpdir.path,
|
|
},
|
|
returnOnExit: returnOnExitEnvToValue(process.env),
|
|
});
|
|
|
|
// Validate the getImportObject helper
|
|
assert.strictEqual(wasiPreview1.wasiImport,
|
|
wasiPreview1.getImportObject().wasi_snapshot_preview1);
|
|
|
|
(async () => {
|
|
const importObject = { ...wasiPreview1.getImportObject() };
|
|
if (args.values.target === 'wasm32-wasip1-threads') {
|
|
let nextTid = 43;
|
|
const workers = [];
|
|
const terminateAllThreads = () => {
|
|
workers.forEach((w) => w.terminate());
|
|
};
|
|
const proc_exit = importObject.wasi_snapshot_preview1.proc_exit;
|
|
importObject.wasi_snapshot_preview1.proc_exit = function(code) {
|
|
terminateAllThreads();
|
|
return proc_exit.call(this, code);
|
|
};
|
|
const spawn = (startArg, threadId) => {
|
|
const tid = nextTid++;
|
|
const name = `pthread-${tid}`;
|
|
const sab = new SharedArrayBuffer(8 + 8192);
|
|
const result = new Int32Array(sab);
|
|
|
|
const workerData = {
|
|
name,
|
|
startArg,
|
|
tid,
|
|
wasmModule,
|
|
memory: importObject.env.memory,
|
|
result,
|
|
};
|
|
|
|
const worker = new Worker(__filename, {
|
|
name,
|
|
argv: process.argv.slice(2),
|
|
execArgv: [
|
|
'--experimental-wasi-unstable-preview1',
|
|
],
|
|
workerData,
|
|
});
|
|
workers[tid] = worker;
|
|
|
|
worker.on('message', ({ cmd, startArg, threadId, tid }) => {
|
|
if (cmd === 'loaded') {
|
|
worker.unref();
|
|
} else if (cmd === 'thread-spawn') {
|
|
spawn(startArg, threadId);
|
|
} else if (cmd === 'cleanup-thread') {
|
|
workers[tid].terminate();
|
|
delete workers[tid];
|
|
} else if (cmd === 'terminate-all-threads') {
|
|
terminateAllThreads();
|
|
}
|
|
});
|
|
|
|
worker.on('error', (e) => {
|
|
terminateAllThreads();
|
|
throw new Error(e);
|
|
});
|
|
|
|
const r = Atomics.wait(result, 0, 0, 1000);
|
|
if (r === 'timed-out') {
|
|
workers[tid].terminate();
|
|
delete workers[tid];
|
|
if (threadId) {
|
|
Atomics.store(threadId, 0, -6);
|
|
Atomics.notify(threadId, 0);
|
|
}
|
|
return -6;
|
|
}
|
|
if (Atomics.load(result, 0) !== 0) {
|
|
const decoder = new TextDecoder();
|
|
const nameLength = Atomics.load(result, 1);
|
|
const messageLength = Atomics.load(result, 2);
|
|
const stackLength = Atomics.load(result, 3);
|
|
const name = decoder.decode(sab.slice(16, 16 + nameLength));
|
|
const message = decoder.decode(sab.slice(16 + nameLength, 16 + nameLength + messageLength));
|
|
const stack = decoder.decode(
|
|
sab.slice(16 + nameLength + messageLength,
|
|
16 + nameLength + messageLength + stackLength));
|
|
const ErrorConstructor = globalThis[name] ?? (
|
|
name === 'RuntimeError' ? (WebAssembly.RuntimeError ?? Error) : Error);
|
|
const error = new ErrorConstructor(message);
|
|
Object.defineProperty(error, 'stack', {
|
|
value: stack,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
});
|
|
Object.defineProperty(error, 'name', {
|
|
value: name,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
});
|
|
throw error;
|
|
}
|
|
if (threadId) {
|
|
Atomics.store(threadId, 0, tid);
|
|
Atomics.notify(threadId, 0);
|
|
}
|
|
return tid;
|
|
};
|
|
const memory = isMainThread ? new WebAssembly.Memory({
|
|
initial: 16777216 / 65536,
|
|
maximum: 2147483648 / 65536,
|
|
shared: true,
|
|
}) : workerData.memory;
|
|
importObject.env ??= {};
|
|
importObject.env.memory = memory;
|
|
importObject.wasi = {
|
|
'thread-spawn': (startArg) => {
|
|
if (isMainThread) {
|
|
return spawn(startArg);
|
|
}
|
|
const threadIdBuffer = new SharedArrayBuffer(4);
|
|
const id = new Int32Array(threadIdBuffer);
|
|
Atomics.store(id, 0, -1);
|
|
parentPort.postMessage({
|
|
cmd: 'thread-spawn',
|
|
startArg,
|
|
threadId: id,
|
|
});
|
|
Atomics.wait(id, 0, -1);
|
|
const tid = Atomics.load(id, 0);
|
|
return tid;
|
|
},
|
|
};
|
|
}
|
|
let wasmModule;
|
|
let instancePreview1;
|
|
try {
|
|
if (isMainThread) {
|
|
const modulePathPreview1 = path.join(wasmDir, `${args.positionals[0]}.wasm`);
|
|
const bufferPreview1 = fs.readFileSync(modulePathPreview1);
|
|
wasmModule = await WebAssembly.compile(bufferPreview1);
|
|
} else {
|
|
wasmModule = workerData.wasmModule;
|
|
}
|
|
instancePreview1 = await WebAssembly.instantiate(wasmModule, importObject);
|
|
|
|
if (isMainThread) {
|
|
wasiPreview1.start(instancePreview1);
|
|
} else {
|
|
wasiPreview1.finalizeBindings(instancePreview1, {
|
|
memory: importObject.env.memory,
|
|
});
|
|
parentPort.postMessage({ cmd: 'loaded' });
|
|
Atomics.store(workerData.result, 0, 0);
|
|
Atomics.notify(workerData.result, 0);
|
|
}
|
|
} catch (e) {
|
|
if (!isMainThread) {
|
|
const encoder = new TextEncoder();
|
|
const nameBuffer = encoder.encode(e.name);
|
|
const messageBuffer = encoder.encode(e.message);
|
|
const stackBuffer = encoder.encode(e.stack);
|
|
Atomics.store(workerData.result, 0, 1);
|
|
Atomics.store(workerData.result, 1, nameBuffer.length);
|
|
Atomics.store(workerData.result, 2, messageBuffer.length);
|
|
Atomics.store(workerData.result, 3, stackBuffer.length);
|
|
const u8arr = new Uint8Array(workerData.result.buffer);
|
|
u8arr.set(nameBuffer, 16);
|
|
u8arr.set(messageBuffer, 16 + nameBuffer.length);
|
|
u8arr.set(stackBuffer, 16 + nameBuffer.length + messageBuffer.length);
|
|
Atomics.notify(workerData.result, 0);
|
|
}
|
|
throw e;
|
|
}
|
|
if (!isMainThread) {
|
|
try {
|
|
instancePreview1.exports.wasi_thread_start(workerData.tid, workerData.startArg);
|
|
} catch (err) {
|
|
if (err instanceof WebAssembly.RuntimeError) {
|
|
parentPort.postMessage({ cmd: 'terminate-all-threads' });
|
|
}
|
|
throw err
|
|
}
|
|
|
|
parentPort.postMessage({ cmd: 'cleanup-thread', tid: workerData.tid });
|
|
}
|
|
})().then(common.mustCall());
|