mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 05:38:47 +02:00
lib: expose setupInstance
method on WASI class
PR-URL: https://github.com/nodejs/node/pull/57214 Reviewed-By: Guy Bedford <guybedford@gmail.com>
This commit is contained in:
parent
c5c696547e
commit
8cc5e57af3
8 changed files with 256 additions and 25 deletions
|
@ -243,6 +243,28 @@ export, then an exception is thrown.
|
|||
|
||||
If `initialize()` is called more than once, an exception is thrown.
|
||||
|
||||
### `wasi.finalizeBindings(instance[, options])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `instance` {WebAssembly.Instance}
|
||||
* `options` {Object}
|
||||
* `memory` {WebAssembly.Memory} **Default:** `instance.exports.memory`.
|
||||
|
||||
Set up WASI host bindings to `instance` without calling `initialize()`
|
||||
or `start()`. This method is useful when the WASI module is instantiated in
|
||||
child threads for sharing the memory across threads.
|
||||
|
||||
`finalizeBindings()` requires that either `instance` exports a
|
||||
[`WebAssembly.Memory`][] named `memory` or user specify a
|
||||
[`WebAssembly.Memory`][] object in `options.memory`. If the `memory` is invalid
|
||||
an exception is thrown.
|
||||
|
||||
`start()` and `initialize()` will call `finalizeBindings()` internally.
|
||||
If `finalizeBindings()` is called more than once, an exception is thrown.
|
||||
|
||||
### `wasi.wasiImport`
|
||||
|
||||
<!-- YAML
|
||||
|
|
35
lib/wasi.js
35
lib/wasi.js
|
@ -34,15 +34,6 @@ const kBindingName = Symbol('kBindingName');
|
|||
|
||||
emitExperimentalWarning('WASI');
|
||||
|
||||
|
||||
function setupInstance(self, instance) {
|
||||
validateObject(instance, 'instance');
|
||||
validateObject(instance.exports, 'instance.exports');
|
||||
|
||||
self[kInstance] = instance;
|
||||
self[kSetMemory](instance.exports.memory);
|
||||
}
|
||||
|
||||
class WASI {
|
||||
constructor(options = kEmptyObject) {
|
||||
validateObject(options, 'options');
|
||||
|
@ -118,14 +109,25 @@ class WASI {
|
|||
this[kInstance] = undefined;
|
||||
}
|
||||
|
||||
// Must not export _initialize, must export _start
|
||||
start(instance) {
|
||||
finalizeBindings(instance, {
|
||||
memory = instance?.exports?.memory,
|
||||
} = {}) {
|
||||
if (this[kStarted]) {
|
||||
throw new ERR_WASI_ALREADY_STARTED();
|
||||
}
|
||||
this[kStarted] = true;
|
||||
|
||||
setupInstance(this, instance);
|
||||
validateObject(instance, 'instance');
|
||||
validateObject(instance.exports, 'instance.exports');
|
||||
|
||||
this[kSetMemory](memory);
|
||||
|
||||
this[kInstance] = instance;
|
||||
this[kStarted] = true;
|
||||
}
|
||||
|
||||
// Must not export _initialize, must export _start
|
||||
start(instance) {
|
||||
this.finalizeBindings(instance);
|
||||
|
||||
const { _start, _initialize } = this[kInstance].exports;
|
||||
|
||||
|
@ -145,12 +147,7 @@ class WASI {
|
|||
|
||||
// Must not export _start, may optionally export _initialize
|
||||
initialize(instance) {
|
||||
if (this[kStarted]) {
|
||||
throw new ERR_WASI_ALREADY_STARTED();
|
||||
}
|
||||
this[kStarted] = true;
|
||||
|
||||
setupInstance(this, instance);
|
||||
this.finalizeBindings(instance);
|
||||
|
||||
const { _start, _initialize } = this[kInstance].exports;
|
||||
|
||||
|
|
194
test/fixtures/wasi-preview-1.js
vendored
194
test/fixtures/wasi-preview-1.js
vendored
|
@ -5,8 +5,21 @@ 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;
|
||||
|
@ -36,13 +49,182 @@ const wasiPreview1 = new WASI({
|
|||
// Validate the getImportObject helper
|
||||
assert.strictEqual(wasiPreview1.wasiImport,
|
||||
wasiPreview1.getImportObject().wasi_snapshot_preview1);
|
||||
const modulePathPreview1 = path.join(wasmDir, `${process.argv[2]}.wasm`);
|
||||
const bufferPreview1 = fs.readFileSync(modulePathPreview1);
|
||||
|
||||
(async () => {
|
||||
const { instance: instancePreview1 } =
|
||||
await WebAssembly.instantiate(bufferPreview1,
|
||||
wasiPreview1.getImportObject());
|
||||
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);
|
||||
|
||||
wasiPreview1.start(instancePreview1);
|
||||
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());
|
||||
|
|
|
@ -6,6 +6,9 @@ CFLAGS = -D_WASI_EMULATED_PROCESS_CLOCKS -lwasi-emulated-process-clocks
|
|||
OBJ = $(patsubst c/%.c, wasm/%.wasm, $(wildcard c/*.c))
|
||||
all: $(OBJ)
|
||||
|
||||
wasm/pthread.wasm : c/pthread.c
|
||||
$(CC) $< $(CFLAGS) --target=wasm32-wasi-threads -pthread -matomics -mbulk-memory -Wl,--import-memory,--export-memory,--shared-memory,--max-memory=2147483648 --sysroot=$(SYSROOT) -s -o $@
|
||||
|
||||
wasm/%.wasm : c/%.c
|
||||
$(CC) $< $(CFLAGS) --target=$(TARGET) --sysroot=$(SYSROOT) -s -o $@
|
||||
|
||||
|
|
24
test/wasi/c/pthread.c
Normal file
24
test/wasi/c/pthread.c
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void* worker(void* data) {
|
||||
int* result = (int*) data;
|
||||
sleep(1);
|
||||
*result = 42;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t thread = NULL;
|
||||
int result = 0;
|
||||
|
||||
int r = pthread_create(&thread, NULL, worker, &result);
|
||||
assert(r == 0);
|
||||
|
||||
r = pthread_join(thread, NULL);
|
||||
assert(r == 0);
|
||||
|
||||
assert(result == 42);
|
||||
return 0;
|
||||
}
|
|
@ -26,3 +26,4 @@ testWasiPreview1(['preopen_populates']);
|
|||
testWasiPreview1(['stat']);
|
||||
testWasiPreview1(['sock']);
|
||||
testWasiPreview1(['write_file']);
|
||||
testWasiPreview1(['--target=wasm32-wasip1-threads', 'pthread']);
|
||||
|
|
BIN
test/wasi/wasm/pthread.wasm
Executable file
BIN
test/wasi/wasm/pthread.wasm
Executable file
Binary file not shown.
|
@ -46,6 +46,8 @@ const customTypesMap = {
|
|||
'bigint': `${jsDocPrefix}Reference/Global_Objects/BigInt`,
|
||||
'WebAssembly.Instance':
|
||||
`${jsDocPrefix}Reference/Global_Objects/WebAssembly/Instance`,
|
||||
'WebAssembly.Memory':
|
||||
`${jsDocPrefix}Reference/Global_Objects/WebAssembly/Memory`,
|
||||
|
||||
'Blob': 'buffer.html#class-blob',
|
||||
'File': 'buffer.html#class-file',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue