Skip to content

Commit b59421e

Browse files
committed
add loadNapiModule and loadNapiModuleSync
1 parent eefa90b commit b59421e

File tree

7 files changed

+476
-127
lines changed

7 files changed

+476
-127
lines changed

packages/core/index.d.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export declare interface InitOptions {
3838
table?: WebAssembly.Table
3939
}
4040

41-
export declare interface NapiModule {
41+
export declare interface NapiModule<ChildThread extends boolean> {
4242
imports: {
4343
env: any
4444
napi: any
@@ -47,6 +47,7 @@ export declare interface NapiModule {
4747
exports: any
4848
loaded: boolean
4949
filename: string
50+
childThread: ChildThread
5051
emnapi: {
5152
syncMemory<T extends ArrayBuffer | ArrayBufferView> (
5253
js_to_wasm: boolean,
@@ -60,4 +61,46 @@ export declare interface NapiModule {
6061
init (options: InitOptions): any
6162
}
6263

63-
export function createNapiModule (options: CreateOptions): NapiModule
64+
export declare function createNapiModule<T extends CreateOptions> (
65+
options: T
66+
): NapiModule<[T['childThread']] extends [boolean] ? T['childThread'] : false>
67+
68+
export declare type LoadOptions<ChildThread extends boolean> = {
69+
wasi?: {
70+
readonly wasiImport?: Record<string, any>
71+
initialize (instance: object): void
72+
getImportObject? (): any
73+
}
74+
overwriteImports?: (importObject: WebAssembly.Imports) => WebAssembly.Imports
75+
postMessage?: (msg: any) => any
76+
} & (
77+
[ChildThread] extends [true]
78+
? {
79+
tid: number
80+
arg: number
81+
}
82+
: {})
83+
84+
export declare function loadNapiModule (
85+
napiModule: NapiModule<false>,
86+
wasmInput: string | URL | BufferSource | WebAssembly.Module,
87+
options?: LoadOptions<false>
88+
): Promise<WebAssembly.WebAssemblyInstantiatedSource>
89+
export declare function loadNapiModule (
90+
napiModule: NapiModule<true>,
91+
wasmInput: string | URL | BufferSource | WebAssembly.Module,
92+
options: LoadOptions<true>
93+
): Promise<WebAssembly.WebAssemblyInstantiatedSource>
94+
95+
export declare function loadNapiModuleSync (
96+
napiModule: NapiModule<false>,
97+
wasmInput: string | URL | BufferSource | WebAssembly.Module,
98+
options?: LoadOptions<false>
99+
): WebAssembly.WebAssemblyInstantiatedSource
100+
export declare function loadNapiModuleSync (
101+
napiModule: NapiModule<true>,
102+
wasmInput: string | URL | BufferSource | WebAssembly.Module,
103+
options: LoadOptions<true>
104+
): WebAssembly.WebAssemblyInstantiatedSource
105+
106+
export declare function handleMessage (msg: { data: any }, callback: (type: string, payload: any) => any): void

packages/core/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export { createNapiModule } from './module.js'
2+
export { loadNapiModule, loadNapiModuleSync } from './load.js'
3+
export { handleMessage } from './worker.js'
24

35
export const version = __VERSION__

packages/core/src/load.js

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/* eslint-disable camelcase */
2+
3+
/** @type {typeof WebAssembly} */
4+
const _WebAssembly = typeof WebAssembly !== 'undefined'
5+
? WebAssembly
6+
: typeof WXWebAssembly !== 'undefined'
7+
// eslint-disable-next-line no-undef
8+
? WXWebAssembly
9+
: undefined
10+
11+
function validateImports (imports) {
12+
if (imports && typeof imports !== 'object') {
13+
throw new TypeError('imports must be an object or undefined')
14+
}
15+
}
16+
17+
function fetchWasm (strOrUrl, imports) {
18+
if (typeof wx !== 'undefined' && typeof __wxConfig !== 'undefined') {
19+
return _WebAssembly.instantiate(strOrUrl, imports)
20+
}
21+
return fetch(strOrUrl)
22+
.then(response => response.arrayBuffer())
23+
.then(buffer => _WebAssembly.instantiate(buffer, imports))
24+
}
25+
26+
/**
27+
* @param {string | URL | BufferSource | WebAssembly.Module} wasmInput
28+
* @param {WebAssembly.Imports=} imports
29+
* @returns {Promise<WebAssembly.WebAssemblyInstantiatedSource>}
30+
*/
31+
function load (wasmInput, imports) {
32+
validateImports(imports)
33+
imports = imports != null ? imports : {}
34+
35+
if ((wasmInput instanceof ArrayBuffer) || ArrayBuffer.isView(wasmInput)) {
36+
return _WebAssembly.instantiate(wasmInput, imports)
37+
}
38+
39+
if (wasmInput instanceof _WebAssembly.Module) {
40+
return _WebAssembly.instantiate(wasmInput, imports).then((instance) => {
41+
return { instance, module: wasmInput }
42+
})
43+
}
44+
45+
if (typeof wasmInput !== 'string' && !(wasmInput instanceof URL)) {
46+
throw new TypeError('Invalid source')
47+
}
48+
49+
let source
50+
if (typeof _WebAssembly.instantiateStreaming === 'function') {
51+
let responsePromise
52+
try {
53+
responsePromise = fetch(wasmInput)
54+
source = _WebAssembly.instantiateStreaming(responsePromise, imports).catch(() => {
55+
return fetchWasm(wasmInput, imports)
56+
})
57+
} catch (_) {
58+
source = fetchWasm(wasmInput, imports)
59+
}
60+
} else {
61+
source = fetchWasm(wasmInput, imports)
62+
}
63+
return source
64+
}
65+
66+
/**
67+
* @param {BufferSource | WebAssembly.Module} wasmInput
68+
* @param {WebAssembly.Imports=} imports
69+
* @returns {WebAssembly.WebAssemblyInstantiatedSource}
70+
*/
71+
function loadSync (wasmInput, imports) {
72+
validateImports(imports)
73+
imports = imports != null ? imports : {}
74+
75+
/** @type {WebAssembly.Module} */
76+
let module
77+
78+
if ((wasmInput instanceof ArrayBuffer) || ArrayBuffer.isView(wasmInput)) {
79+
module = new _WebAssembly.Module(wasmInput)
80+
} else if (wasmInput instanceof WebAssembly.Module) {
81+
module = wasmInput
82+
} else {
83+
throw new TypeError('Invalid source')
84+
}
85+
86+
const instance = new _WebAssembly.Instance(module, imports)
87+
const source = { instance, module }
88+
89+
return source
90+
}
91+
92+
function loadNapiModuleImpl (loadFn, napiModule, wasmInput, options) {
93+
if (!napiModule) {
94+
throw new TypeError('Invalid napiModule')
95+
}
96+
97+
options = options == null ? {} : options
98+
const wasi = options.wasi
99+
const env = Object.assign({}, napiModule.imports.env, napiModule.imports.napi, napiModule.imports.emnapi)
100+
let importObject = {
101+
env,
102+
napi: napiModule.imports.napi,
103+
emnapi: napiModule.imports.emnapi,
104+
wasi: {
105+
'thread-spawn': function __imported_wasi_thread_spawn (startArg) {
106+
return napiModule.spawnThread(startArg, undefined)
107+
}
108+
}
109+
}
110+
111+
if (wasi) {
112+
Object.assign(
113+
importObject,
114+
typeof wasi.getImportObject === 'function'
115+
? wasi.getImportObject()
116+
: { wasi_snapshot_preview1: wasi.wasiImport }
117+
)
118+
}
119+
120+
const overwriteImports = options.overwriteImports
121+
if (typeof overwriteImports === 'function') {
122+
const newImportObject = overwriteImports(importObject)
123+
if (typeof newImportObject === 'object' && newImportObject !== null) {
124+
importObject = newImportObject
125+
}
126+
}
127+
128+
const tid = options.tid
129+
const arg = options.arg
130+
if (napiModule.childThread) {
131+
if (typeof tid !== 'number') {
132+
throw new TypeError('options.tid is not a number')
133+
}
134+
if (typeof arg !== 'number') {
135+
throw new TypeError('options.arg is not a number')
136+
}
137+
}
138+
139+
const postMsg = typeof options.postMessage === 'function'
140+
? typeof options.postMessage
141+
: typeof postMessage === 'function'
142+
? postMessage
143+
: function () {}
144+
145+
return loadFn(wasmInput, importObject, (err, source) => {
146+
if (err) {
147+
if (napiModule.childThread) {
148+
postMsg({
149+
__emnapi__: {
150+
type: 'loaded',
151+
payload: {
152+
tid,
153+
err
154+
}
155+
}
156+
})
157+
}
158+
throw err
159+
}
160+
161+
let instance = source.instance
162+
const exportMemory = source.instance.exports.memory instanceof _WebAssembly.Memory
163+
const importMemory = importObject.env.memory instanceof _WebAssembly.Memory
164+
/** @type {WebAssembly.Memory} */
165+
const memory = exportMemory ? source.instance.exports.memory : importMemory ? importObject.env.memory : undefined
166+
if (!memory) {
167+
throw new Error('memory is neither exported nor imported')
168+
}
169+
if (wasi && !exportMemory && importMemory) {
170+
instance = {
171+
exports: Object.assign({}, source.instance.exports, { memory })
172+
}
173+
}
174+
const module = source.module
175+
if (wasi) {
176+
if (napiModule.childThread) {
177+
// https://github.com/nodejs/help/issues/4102
178+
const noop = () => {}
179+
const exportsProxy = new Proxy({}, {
180+
get (t, p, r) {
181+
if (p === 'memory') {
182+
return memory
183+
}
184+
if (p === '_initialize') {
185+
return noop
186+
}
187+
return Reflect.get(instance.exports, p, r)
188+
}
189+
})
190+
instance = new Proxy(instance, {
191+
get (target, p, receiver) {
192+
if (p === 'exports') {
193+
return exportsProxy
194+
}
195+
return Reflect.get(target, p, receiver)
196+
}
197+
})
198+
}
199+
wasi.initialize(instance)
200+
}
201+
202+
if (napiModule.childThread) {
203+
postMsg({
204+
__emnapi__: {
205+
type: 'loaded',
206+
payload: {
207+
tid,
208+
err: null
209+
}
210+
}
211+
})
212+
instance.exports.wasi_thread_start(tid, arg)
213+
} else {
214+
napiModule.init({
215+
instance,
216+
module,
217+
memory,
218+
table: instance.exports.__indirect_function_table
219+
})
220+
}
221+
222+
return { instance, module }
223+
})
224+
}
225+
226+
/**
227+
* @param {import('@emnapi/core').NapiModule} napiModule
228+
* @param {string | URL | BufferSource | WebAssembly.Module} wasmInput
229+
* @param {any} options
230+
* @returns {Promise<WebAssembly.WebAssemblyInstantiatedSource>}
231+
*/
232+
export function loadNapiModule (napiModule, wasmInput, options) {
233+
return loadNapiModuleImpl((wasmInput, importObject, callback) => {
234+
return load(wasmInput, importObject).then((source) => {
235+
return callback(null, source)
236+
}, err => {
237+
return callback(err)
238+
})
239+
}, napiModule, wasmInput, options)
240+
}
241+
242+
/**
243+
* @param {import('@emnapi/core').NapiModule} napiModule
244+
* @param {BufferSource | WebAssembly.Module} wasmInput
245+
* @param {any} options
246+
* @returns {WebAssembly.WebAssemblyInstantiatedSource}
247+
*/
248+
export function loadNapiModuleSync (napiModule, wasmInput, options) {
249+
return loadNapiModuleImpl((wasmInput, importObject, callback) => {
250+
let source
251+
try {
252+
source = loadSync(wasmInput, importObject)
253+
} catch (err) {
254+
return callback(err)
255+
}
256+
return callback(null, source)
257+
}, napiModule, wasmInput, options)
258+
}

packages/core/src/worker.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function handleMessage (e, callback) {
2+
if (e.data.__emnapi__) {
3+
const type = e.data.__emnapi__.type
4+
const payload = e.data.__emnapi__.payload
5+
callback(type, payload)
6+
}
7+
}

packages/emnapi/src/core/init.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ declare interface INapiModule {
3232
emnapi: any
3333
loaded: boolean
3434
filename: string
35+
childThread: boolean
3536
// PThread: {
3637
// pthreads: any[]
3738
// }
@@ -73,6 +74,7 @@ var napiModule: INapiModule = {
7374
emnapi: {},
7475
loaded: false,
7576
filename: '',
77+
childThread: Boolean(options.childThread),
7678

7779
// PThread: {
7880
// pthreads: [undefined]

0 commit comments

Comments
 (0)