204 lines
5.8 KiB
Markdown
204 lines
5.8 KiB
Markdown
# @emnapi/wasi-threads
|
|
|
|
This package makes [wasi-threads proposal](https://github.com/WebAssembly/wasi-threads) based WASI modules work in Node.js and browser.
|
|
|
|
## Quick Start
|
|
|
|
`index.html`
|
|
|
|
```html
|
|
<script src="./node_modules/@tybys/wasm-util/dist/wasm-util.js"></script>
|
|
<script src="./node_modules/@emnapi/wasi-threads/dist/wasi-threads.js"></script>
|
|
<script src="./index.js"></script>
|
|
```
|
|
|
|
If your application will block browser main thread (for example `pthread_join`), please run it in worker instead.
|
|
|
|
```html
|
|
<script>
|
|
// pthread_join (Atomics.wait) cannot be called in browser main thread
|
|
new Worker('./index.js')
|
|
</script>
|
|
```
|
|
|
|
`index.js`
|
|
|
|
```js
|
|
const ENVIRONMENT_IS_NODE =
|
|
typeof process === 'object' && process !== null &&
|
|
typeof process.versions === 'object' && process.versions !== null &&
|
|
typeof process.versions.node === 'string';
|
|
|
|
(function (main) {
|
|
if (ENVIRONMENT_IS_NODE) {
|
|
main(require)
|
|
} else {
|
|
if (typeof importScripts === 'function') {
|
|
importScripts('./node_modules/@tybys/wasm-util/dist/wasm-util.js')
|
|
importScripts('./node_modules/@emnapi/wasi-threads/dist/wasi-threads.js')
|
|
}
|
|
const nodeWasi = { WASI: globalThis.wasmUtil.WASI }
|
|
const nodeWorkerThreads = {
|
|
Worker: globalThis.Worker
|
|
}
|
|
const _require = function (request) {
|
|
if (request === 'node:wasi' || request === 'wasi') return nodeWasi
|
|
if (request === 'node:worker_threads' || request === 'worker_threads') return nodeWorkerThreads
|
|
if (request === '@emnapi/wasi-threads') return globalThis.wasiThreads
|
|
throw new Error('Can not find module: ' + request)
|
|
}
|
|
main(_require)
|
|
}
|
|
})(async function (require) {
|
|
const { WASI } = require('wasi')
|
|
const { Worker } = require('worker_threads')
|
|
const { WASIThreads } = require('@emnapi/wasi-threads')
|
|
|
|
const wasi = new WASI({
|
|
version: 'preview1'
|
|
})
|
|
const wasiThreads = new WASIThreads({
|
|
wasi,
|
|
|
|
/**
|
|
* avoid Atomics.wait() deadlock during thread creation in browser
|
|
* see https://emscripten.org/docs/tools_reference/settings_reference.html#pthread-pool-size
|
|
*/
|
|
reuseWorker: ENVIRONMENT_IS_NODE
|
|
? false
|
|
: {
|
|
size: 4 /** greater than actual needs (2) */,
|
|
strict: true
|
|
},
|
|
|
|
/**
|
|
* Synchronous thread creation
|
|
* pthread_create will not return until thread worker actually starts
|
|
*/
|
|
waitThreadStart: typeof window === 'undefined' ? 1000 : false,
|
|
|
|
onCreateWorker: () => {
|
|
return new Worker('./worker.js', {
|
|
execArgv: ['--experimental-wasi-unstable-preview1']
|
|
})
|
|
}
|
|
})
|
|
const memory = new WebAssembly.Memory({
|
|
initial: 16777216 / 65536,
|
|
maximum: 2147483648 / 65536,
|
|
shared: true
|
|
})
|
|
let input
|
|
const file = 'path/to/your/wasi-module.wasm'
|
|
try {
|
|
input = require('fs').readFileSync(require('path').join(__dirname, file))
|
|
} catch (err) {
|
|
const response = await fetch(file)
|
|
input = await response.arrayBuffer()
|
|
}
|
|
let { module, instance } = await WebAssembly.instantiate(input, {
|
|
env: { memory },
|
|
wasi_snapshot_preview1: wasi.wasiImport,
|
|
...wasiThreads.getImportObject()
|
|
})
|
|
|
|
wasiThreads.setup(instance, module, memory)
|
|
await wasiThreads.preloadWorkers()
|
|
|
|
if (typeof instance.exports._start === 'function') {
|
|
return wasi.start(instance)
|
|
} else {
|
|
wasi.initialize(instance)
|
|
// instance.exports.exported_wasm_function()
|
|
}
|
|
})
|
|
```
|
|
|
|
`worker.js`
|
|
|
|
```js
|
|
(function (main) {
|
|
const ENVIRONMENT_IS_NODE =
|
|
typeof process === 'object' && process !== null &&
|
|
typeof process.versions === 'object' && process.versions !== null &&
|
|
typeof process.versions.node === 'string'
|
|
|
|
if (ENVIRONMENT_IS_NODE) {
|
|
const _require = function (request) {
|
|
return require(request)
|
|
}
|
|
|
|
const _init = function () {
|
|
const nodeWorkerThreads = require('worker_threads')
|
|
const parentPort = nodeWorkerThreads.parentPort
|
|
|
|
parentPort.on('message', (data) => {
|
|
globalThis.onmessage({ data })
|
|
})
|
|
|
|
Object.assign(globalThis, {
|
|
self: globalThis,
|
|
require,
|
|
Worker: nodeWorkerThreads.Worker,
|
|
importScripts: function (f) {
|
|
(0, eval)(require('fs').readFileSync(f, 'utf8') + '//# sourceURL=' + f)
|
|
},
|
|
postMessage: function (msg) {
|
|
parentPort.postMessage(msg)
|
|
}
|
|
})
|
|
}
|
|
|
|
main(_require, _init)
|
|
} else {
|
|
importScripts('./node_modules/@tybys/wasm-util/dist/wasm-util.js')
|
|
importScripts('./node_modules/@emnapi/wasi-threads/dist/wasi-threads.js')
|
|
|
|
const nodeWasi = { WASI: globalThis.wasmUtil.WASI }
|
|
const _require = function (request) {
|
|
if (request === '@emnapi/wasi-threads') return globalThis.wasiThreads
|
|
if (request === 'node:wasi' || request === 'wasi') return nodeWasi
|
|
throw new Error('Can not find module: ' + request)
|
|
}
|
|
const _init = function () {}
|
|
main(_require, _init)
|
|
}
|
|
})(function main (require, init) {
|
|
init()
|
|
|
|
const { WASI } = require('wasi')
|
|
const { ThreadMessageHandler, WASIThreads } = require('@emnapi/wasi-threads')
|
|
|
|
const handler = new ThreadMessageHandler({
|
|
async onLoad ({ wasmModule, wasmMemory }) {
|
|
const wasi = new WASI({
|
|
version: 'preview1'
|
|
})
|
|
|
|
const wasiThreads = new WASIThreads({
|
|
wasi,
|
|
childThread: true
|
|
})
|
|
|
|
const originalInstance = await WebAssembly.instantiate(wasmModule, {
|
|
env: {
|
|
memory: wasmMemory,
|
|
},
|
|
wasi_snapshot_preview1: wasi.wasiImport,
|
|
...wasiThreads.getImportObject()
|
|
})
|
|
|
|
// must call `initialize` instead of `start` in child thread
|
|
const instance = wasiThreads.initialize(originalInstance, wasmModule, wasmMemory)
|
|
|
|
return { module: wasmModule, instance }
|
|
}
|
|
})
|
|
|
|
globalThis.onmessage = function (e) {
|
|
handler.handle(e)
|
|
// handle other messages
|
|
}
|
|
})
|
|
```
|