Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi committed May 9, 2024
1 parent 01b5fdd commit f48e21a
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 51 deletions.
10 changes: 9 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@
"program": "${file}",
"args": [],
"preLaunchTask": "CMake: build ${input:target}"
}
},
{
"type": "node",
"request": "launch",
"name": "wasi-threads test",
"runtimeArgs": [],
"program": "${workspaceFolder}/packages/wasi-threads/test/index.js",
"args": []
},
],
"inputs": [
{
Expand Down
51 changes: 2 additions & 49 deletions packages/core/src/load.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WASIThreads } from '@emnapi/wasi-threads'
import { WASIThreads, createInstanceProxy } from '@emnapi/wasi-threads'
import { type InputType, load, loadSync } from './util'
import { createNapiModule } from './emnapi/index'
import type { CreateOptions, NapiModule } from './emnapi/index'
Expand Down Expand Up @@ -131,54 +131,7 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
const module = source.module
if (wasi) {
if (napiModule.childThread) {
// https://github.com/nodejs/help/issues/4102
const createHandler = function (target: WebAssembly.Exports): ProxyHandler<WebAssembly.Exports> {
const handlers = [
'apply',
'construct',
'defineProperty',
'deleteProperty',
'get',
'getOwnPropertyDescriptor',
'getPrototypeOf',
'has',
'isExtensible',
'ownKeys',
'preventExtensions',
'set',
'setPrototypeOf'
]
const handler: ProxyHandler<WebAssembly.Exports> = {}
for (let i = 0; i < handlers.length; i++) {
const name = handlers[i] as keyof ProxyHandler<WebAssembly.Exports>
handler[name] = function () {
const args = Array.prototype.slice.call(arguments, 1)
args.unshift(target)
return (Reflect[name] as any).apply(Reflect, args)
}
}
return handler
}
const handler = createHandler(originalExports)
const noop = (): void => {}
handler.get = function (_target, p, receiver) {
if (p === 'memory') {
return memory
}
if (p === '_initialize') {
return noop
}
return Reflect.get(originalExports, p, receiver)
}
const exportsProxy = new Proxy(Object.create(null), handler)
instance = new Proxy(instance, {
get (target, p, receiver) {
if (p === 'exports') {
return exportsProxy
}
return Reflect.get(target, p, receiver)
}
})
instance = createInstanceProxy(instance, memory)
}
wasi.initialize(instance)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/wasi-threads/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
node_modules
/dist
/src/emnapi/**/*.js
/test/**/*.wasm
/test/**/*.wat
1 change: 1 addition & 0 deletions packages/wasi-threads/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules
.npmignore
api-extractor.json
tsconfig.json
/test
2 changes: 2 additions & 0 deletions packages/wasi-threads/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export { WASIThreads } from './wasi-threads'

export { MessageHandler } from './worker'
export type { OnLoadData, HandleOptions } from './worker'

export { ExecutionModel, createInstanceProxy } from './proxy'
72 changes: 72 additions & 0 deletions packages/wasi-threads/src/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/** @public */
export enum ExecutionModel {
Command = 'command',
Reactor = 'reactor'
}

/** @public */
export function createInstanceProxy (
instance: WebAssembly.Instance,
memory: WebAssembly.Memory | (() => WebAssembly.Memory),
model: ExecutionModel = ExecutionModel.Reactor
): WebAssembly.Instance {
// https://github.com/nodejs/help/issues/4102
const originalExports = instance.exports
const createHandler = function (target: WebAssembly.Exports): ProxyHandler<WebAssembly.Exports> {
const handlers = [
'apply',
'construct',
'defineProperty',
'deleteProperty',
'get',
'getOwnPropertyDescriptor',
'getPrototypeOf',
'has',
'isExtensible',
'ownKeys',
'preventExtensions',
'set',
'setPrototypeOf'
]
const handler: ProxyHandler<WebAssembly.Exports> = {}
for (let i = 0; i < handlers.length; i++) {
const name = handlers[i] as keyof ProxyHandler<WebAssembly.Exports>
handler[name] = function () {
const args = Array.prototype.slice.call(arguments, 1)
args.unshift(target)
return (Reflect[name] as any).apply(Reflect, args)
}
}
return handler
}
const handler = createHandler(originalExports)
const _initialize = (): void => {}
const _start = (): number => 0
handler.get = function (_target, p, receiver) {
if (p === 'memory') {
return typeof memory === 'function' ? memory() : memory
}
if (p === '_initialize') {
return model === ExecutionModel.Reactor ? _initialize : undefined
}
if (p === '_start') {
return model === ExecutionModel.Command ? _start : undefined
}
return Reflect.get(originalExports, p, receiver)
}
handler.has = function (_target, p) {
if (p === 'memory') return true
if (p === '_initialize') return model === ExecutionModel.Reactor
if (p === '_start') return model === ExecutionModel.Command
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
}
return Reflect.get(target, p, receiver)
}
})
}
99 changes: 98 additions & 1 deletion packages/wasi-threads/test/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,98 @@
// TODO
const fs = require('node:fs')
const { join } = require('node:path')
const { spawnSync } = require('node:child_process')
const { Worker } = require('node:worker_threads')
const { WASI } = require('wasi')
const { WASIThreads, ThreadManager, ExecutionModel } = require('..')

function build (model = ExecutionModel.Reactor) {
const bin = join(process.env.WASI_SDK_PATH, 'bin', 'clang') + (process.platform === 'win32' ? '.exe' : '')
const args = [
'-o', join(__dirname, model === ExecutionModel.Command ? 'main.wasm' : 'lib.wasm'),
'-mbulk-memory',
'-matomics',
`-mexec-model=${model}`,
...(model === ExecutionModel.Command
? [
'-D__WASI_COMMAND__=1'
]
: [
'-Wl,--no-entry'
]
),
'--target=wasm32-wasi-threads',
// '-O3',
'-g',
'-pthread',
'-Wl,--import-memory',
'-Wl,--shared-memory',
'-Wl,--export-memory',
'-Wl,--export-dynamic',
'-Wl,--max-memory=2147483648',
'-Wl,--export=malloc,--export=free',
join(__dirname, 'main.c')
]
console.log(`> "${bin}" ${args.map(s => s.includes(' ') ? `"${s}"` : s).join(' ')}`)
spawnSync(bin, args, {
stdio: 'inherit',
env: process.env
})
}

function run (model = ExecutionModel.Reactor) {
const wasi = new WASI({
version: 'preview1',
env: process.env
})
const wasiThreads = new WASIThreads({
threadManager: new ThreadManager({
printErr: console.error.bind(console),
onCreateWorker: ({ name }) => {
return new Worker(join(__dirname, 'worker.js'), {
name,
workerData: {
name,
model
},
env: process.env,
execArgv: ['--experimental-wasi-unstable-preview1']
})
}
})
})
const memory = new WebAssembly.Memory({
initial: 16777216 / 65536,
maximum: 2147483648 / 65536,
shared: true
})
const file = join(__dirname, model === ExecutionModel.Command ? 'main.wasm' : 'lib.wasm')
return WebAssembly.instantiate(fs.readFileSync(file), {
env: {
memory
},
...wasi.getImportObject(),
...wasiThreads.getImportObject()
}).then(({ module, instance }) => {
wasiThreads.setup(instance, module, memory)
if (model === ExecutionModel.Command) {
return wasi.start(instance)
} else {
wasi.initialize(instance)
return instance.exports.fn()
}
})
}

async function main () {
build(ExecutionModel.Command)
build(ExecutionModel.Reactor)
console.log('-------- command --------')
await run(ExecutionModel.Command)
console.log('-------- reactor --------')
await run(ExecutionModel.Reactor)
}

main().catch(err => {
console.error(err)
process.exit(1)
})
36 changes: 36 additions & 0 deletions packages/wasi-threads/test/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void *print_message_function(void *ptr) {
char *message;
message = (char *)ptr;
printf("%s \n", message);
return NULL;
}

__attribute__((visibility("default")))
int fn() {
pthread_t thread1, thread2;
const char *message1 = "Thread 1";
const char *message2 = "Thread 2";
int iret1, iret2;

iret1 = pthread_create(&thread1, NULL, print_message_function,
(void *)message1);
iret2 = pthread_create(&thread2, NULL, print_message_function,
(void *)message2);

pthread_join(thread1, NULL);
pthread_join(thread2, NULL);

printf("Thread 1 returns: %d\n", iret1);
printf("Thread 2 returns: %d\n", iret2);
return 0;
}

#ifdef __WASI_COMMAND__
int main() {
return fn();
}
#endif
Loading

0 comments on commit f48e21a

Please sign in to comment.