Skip to content

Commit

Permalink
feat: separate @emnapi/wasi-threads package (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi committed May 14, 2024
1 parent e1b1064 commit 4304d7a
Show file tree
Hide file tree
Showing 39 changed files with 2,200 additions and 481 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module.exports = {
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/member-delimiter-style': ['error', {
multiline: {
delimiter: 'none',
Expand Down
40 changes: 39 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- test-*
tags:
- v*
- wasi-threads-v*
pull_request:
paths-ignore:
- '**/*.md'
Expand All @@ -25,6 +26,7 @@ env:

jobs:
build:
timeout-minutes: 15
name: Build
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -93,6 +95,14 @@ jobs:
shell: bash
run: npm run test -w packages/ts-transform-emscripten-esm-library

- name: Test @emnapi/wasi-threads
if: ${{ matrix.target == 'wasm32-wasi-threads' }}
shell: bash
run: |
node ./packages/wasi-threads/test/build.js
npm run test -w packages/wasi-threads
timeout-minutes: 1

# - name: Lint
# run: npm run lint

Expand Down Expand Up @@ -133,7 +143,7 @@ jobs:
release:
name: Release
if: ${{ startsWith(github.event.ref, 'refs/tags') }}
if: ${{ startsWith(github.event.ref, 'refs/tags/v') }}
needs: build
runs-on: ubuntu-latest

Expand Down Expand Up @@ -187,3 +197,31 @@ jobs:
prerelease: false
generate_release_notes: true
files: ./script/emnapi.zip

release-wasi-threads:
name: Release
if: ${{ startsWith(github.event.ref, 'refs/tags/wasi-threads-v') }}
needs: build
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '20.9.0'
registry-url: 'https://registry.npmjs.org'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: NPM Install
shell: bash
run: |
npm install -g node-gyp
npm install
- name: NPM Build
shell: bash
run: npm run build -w packages/wasi-threads

- name: Publish
run: npm publish --ignore-scripts -w packages/wasi-threads
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"packages/rollup-plugin-emscripten-esm-library",
"packages/runtime",
"packages/node",
"packages/wasi-threads",
"packages/emnapi",
"packages/core",
"packages/test",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
},
"./dist/emnapi-core.min.mjs": {
"types": "./dist/emnapi-core.d.mts",
"import": "./dist/emnapi-core.min.mjs",
"require": null
"default": "./dist/emnapi-core.min.mjs"
}
},
"dependencies": {
"@emnapi/wasi-threads": "1.0.0",
"tslib": "^2.4.0"
},
"scripts": {
Expand Down
12 changes: 6 additions & 6 deletions packages/core/script/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const dist = path.join(__dirname, '../dist')
function build () {
compile(path.join(__dirname, '../tsconfig.json'), {
optionsToExtend: {
target: require('typescript').ScriptTarget.ES2019,
target: ts.ScriptTarget.ES2019,
emitDeclarationOnly: true,
declaration: true,
declarationDir: path.join(__dirname, '../lib/typings')
Expand Down Expand Up @@ -108,7 +108,7 @@ function build () {
}
},
{
input: createInput(ts.ScriptTarget.ES2019, false, ['tslib']),
input: createInput(ts.ScriptTarget.ES2019, false, ['tslib', '@emnapi/wasi-threads']),
output: {
file: path.join(dist, 'emnapi-core.cjs.js'),
format: 'cjs',
Expand All @@ -118,7 +118,7 @@ function build () {
}
},
{
input: createInput(ts.ScriptTarget.ES2019, true, ['tslib']),
input: createInput(ts.ScriptTarget.ES2019, true, ['tslib', '@emnapi/wasi-threads']),
output: {
file: path.join(dist, 'emnapi-core.cjs.min.js'),
format: 'cjs',
Expand All @@ -128,7 +128,7 @@ function build () {
}
},
{
input: createInput(ts.ScriptTarget.ES2019, false, ['tslib']),
input: createInput(ts.ScriptTarget.ES2019, false, ['tslib', '@emnapi/wasi-threads']),
output: {
file: path.join(dist, 'emnapi-core.mjs'),
format: 'esm',
Expand All @@ -138,7 +138,7 @@ function build () {
}
},
{
input: createInput(ts.ScriptTarget.ES2019, true, ['tslib']),
input: createInput(ts.ScriptTarget.ES2019, true, ['tslib', '@emnapi/wasi-threads']),
output: {
file: path.join(dist, 'emnapi-core.min.mjs'),
format: 'esm',
Expand All @@ -148,7 +148,7 @@ function build () {
}
},
{
input: createInput(ts.ScriptTarget.ES5, false, ['tslib']),
input: createInput(ts.ScriptTarget.ES5, false, ['tslib', '@emnapi/wasi-threads']),
output: {
file: path.join(dist, 'emnapi-core.esm-bundler.js'),
format: 'esm',
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/emnapi/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Context } from '@emnapi/runtime'
import type { ThreadManager } from '@emnapi/wasi-threads'

/** @public */
export declare interface PointerInfo {
Expand Down Expand Up @@ -34,14 +35,17 @@ export declare interface NapiModule {
len?: number
): T
getMemoryAddress (arrayBufferOrView: ArrayBuffer | ArrayBufferView): PointerInfo
addSendListener (worker: any): boolean
}

init (options: InitOptions): any
spawnThread (startArg: number, errorOrTid?: number): number
startThread (tid: number, startArg: number): void
initWorker (arg: number): void
executeAsyncWork (work: number): void
postMessage?: (msg: any) => any

waitThreadStart: boolean | number
/** @internal */
PThread: ThreadManager
}

/** @public */
Expand Down Expand Up @@ -69,7 +73,7 @@ export declare type BaseCreateOptions = {
nodeBinding?: NodeBinding
reuseWorker?: boolean
asyncWorkPoolSize?: number
waitThreadStart?: boolean
waitThreadStart?: boolean | number
onCreateWorker?: (info: CreateWorkerInfo) => any
print?: (str: string) => void
printErr?: (str: string) => void
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ export type {
export type {
LoadOptions,
InstantiateOptions,
InstantiatedSource,
ReactorWASI
LoadedSource,
InstantiatedSource
} from './load'

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

export type {
InputType
} from './util'

export * from '@emnapi/wasi-threads'
104 changes: 36 additions & 68 deletions packages/core/src/load.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { type WASIInstance, WASIThreads } from '@emnapi/wasi-threads'
import { type InputType, load, loadSync } from './util'
import { createNapiModule } from './emnapi/index'
import type { CreateOptions, NapiModule } from './emnapi/index'

/** @public */
export interface InstantiatedSource extends WebAssembly.WebAssemblyInstantiatedSource {
napiModule: NapiModule
export interface LoadedSource extends WebAssembly.WebAssemblyInstantiatedSource {
usedInstance: WebAssembly.Instance
}

/** @public */
export interface ReactorWASI {
readonly wasiImport?: Record<string, any>
initialize (instance: object): void
getImportObject? (): any
export interface InstantiatedSource extends LoadedSource {
napiModule: NapiModule
}

/** @public */
export interface LoadOptions {
wasi?: ReactorWASI
wasi?: WASIInstance
overwriteImports?: (importObject: WebAssembly.Imports) => WebAssembly.Imports
beforeInit?: (source: WebAssembly.WebAssemblyInstantiatedSource) => void
getMemory?: (exports: WebAssembly.Exports) => WebAssembly.Memory
Expand Down Expand Up @@ -67,25 +66,37 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
}

const wasi = options!.wasi
let wasiThreads: WASIThreads | undefined

let importObject: WebAssembly.Imports = {
env: napiModule.imports.env,
napi: napiModule.imports.napi,
emnapi: napiModule.imports.emnapi,
wasi: {
// eslint-disable-next-line camelcase
'thread-spawn': function __imported_wasi_thread_spawn (startArg: number, errorOrTid: number) {
return napiModule.spawnThread(startArg, errorOrTid)
}
}
emnapi: napiModule.imports.emnapi
}

if (wasi) {
wasiThreads = new WASIThreads(
napiModule.childThread
? {
wasi,
childThread: true,
postMessage: napiModule.postMessage!
}
: {
wasi,
threadManager: napiModule.PThread,
waitThreadStart: napiModule.waitThreadStart
}
)

Object.assign(
importObject,
typeof wasi.getImportObject === 'function'
? wasi.getImportObject()
: { wasi_snapshot_preview1: wasi.wasiImport }
)

Object.assign(importObject, wasiThreads.getImportObject())
}

const overwriteImports = options!.overwriteImports
Expand Down Expand Up @@ -124,58 +135,11 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
instance = { exports }
}
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)
}
})
}
wasi.initialize(instance)
instance = wasiThreads!.initialize(instance, module, memory)
} else {
napiModule.PThread.setup(module, memory)
}

if (beforeInit) {
Expand All @@ -192,7 +156,11 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
table
})

const ret: any = { instance: originalInstance, module }
const ret: any = {
instance: originalInstance,
module,
usedInstance: instance
}
if (!isLoad) {
ret.napiModule = napiModule
}
Expand Down Expand Up @@ -229,7 +197,7 @@ export function loadNapiModule (
/** Only support `BufferSource` or `WebAssembly.Module` on Node.js */
wasmInput: InputType | Promise<InputType>,
options?: LoadOptions
): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
): Promise<LoadedSource> {
if (typeof napiModule !== 'object' || napiModule === null) {
throw new TypeError('Invalid napiModule')
}
Expand All @@ -241,7 +209,7 @@ export function loadNapiModuleSync (
napiModule: NapiModule,
wasmInput: BufferSource | WebAssembly.Module,
options?: LoadOptions
): WebAssembly.WebAssemblyInstantiatedSource {
): LoadedSource {
if (typeof napiModule !== 'object' || napiModule === null) {
throw new TypeError('Invalid napiModule')
}
Expand Down

0 comments on commit 4304d7a

Please sign in to comment.