emnapi 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CMakeLists.txt +160 -0
  2. package/LICENSE +21 -0
  3. package/README.md +875 -0
  4. package/cmake/wasm32.cmake +32 -0
  5. package/dist/library_napi.js +4784 -0
  6. package/include/common.h +26 -0
  7. package/include/emnapi.h +81 -0
  8. package/include/js_native_api.h +560 -0
  9. package/include/js_native_api_types.h +159 -0
  10. package/include/napi-inl.deprecated.h +186 -0
  11. package/include/napi-inl.h +6299 -0
  12. package/include/napi.h +3127 -0
  13. package/include/node_api.h +226 -0
  14. package/include/node_api_types.h +56 -0
  15. package/include/uv/threadpool.h +41 -0
  16. package/include/uv/unix.h +21 -0
  17. package/include/uv.h +134 -0
  18. package/index.d.ts +4 -0
  19. package/index.js +22 -0
  20. package/lib/wasm32/libdlmalloc.a +0 -0
  21. package/lib/wasm32/libemmalloc.a +0 -0
  22. package/lib/wasm32/libemnapi.a +0 -0
  23. package/lib/wasm32-emscripten/libemnapi-mt.a +0 -0
  24. package/lib/wasm32-emscripten/libemnapi.a +0 -0
  25. package/lib/wasm32-emscripten.txt +5 -0
  26. package/lib/wasm32-wasi/libemnapi.a +0 -0
  27. package/lib/wasm32-wasi.txt +4 -0
  28. package/lib/wasm32.txt +4 -0
  29. package/package.json +43 -0
  30. package/src/emnapi.c +1344 -0
  31. package/src/malloc/dlmalloc/dlmalloc.c +92 -0
  32. package/src/malloc/dlmalloc/malloc.c +6395 -0
  33. package/src/malloc/emmalloc/emmalloc.c +1551 -0
  34. package/src/malloc/memcpy.c +136 -0
  35. package/src/malloc/memset.c +98 -0
  36. package/src/malloc/sbrk.c +29 -0
  37. package/src/uv/queue.h +108 -0
  38. package/src/uv/threadpool.c +408 -0
  39. package/src/uv/unix/async.c +206 -0
  40. package/src/uv/unix/core.c +35 -0
  41. package/src/uv/unix/loop.c +36 -0
  42. package/src/uv/unix/thread.c +118 -0
  43. package/src/uv/uv-common.c +51 -0
  44. package/src/uv/uv-common.h +68 -0
package/README.md ADDED
@@ -0,0 +1,875 @@
1
+ # emnapi
2
+
3
+ <p align="center">
4
+ <img src="https://toyobayashi.github.io/emnapi-docs/emnapi.svg" alt="emnapi logo" width="256" />
5
+ </p>
6
+
7
+ [![Build](https://github.com/toyobayashi/emnapi/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/toyobayashi/emnapi/actions/workflows/main.yml)
8
+
9
+ [Node-API](https://nodejs.org/docs/latest/api/n-api.html) implementation for [Emscripten](https://emscripten.org/index.html), [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) and clang `wasm32-unknown-unknown` target, [napi-rs support is comming soon](https://github.com/napi-rs/napi-rs/issues/796).
10
+
11
+ Emscripten is the first class support target, currently thread related APIs are unavailable on `wasm32-unknown-unknown` and `wasm32-wasi` target.
12
+
13
+ This project aims to
14
+
15
+ - Help users port their or existing Node-API native addons to wasm with code change as less as possible.
16
+ - Make runtime behavior matches native Node.js as much as possible.
17
+
18
+ See documentation for more details:
19
+ - [https://toyobayashi.github.io/emnapi-docs/guide/](https://toyobayashi.github.io/emnapi-docs/guide/)
20
+ - [https://emnapi-docs.vercel.app/guide/](https://emnapi-docs.vercel.app/guide/)
21
+
22
+ 中文文档:
23
+ - [https://toyobayashi.github.io/emnapi-docs/zh/guide/](https://toyobayashi.github.io/emnapi-docs/zh/guide/)
24
+ - [https://emnapi-docs.vercel.app/zh/guide/](https://emnapi-docs.vercel.app/zh/guide/)
25
+
26
+ [Full API List](https://toyobayashi.github.io/emnapi-docs/reference/list.html)
27
+
28
+ [How to build Node-API official examples](https://github.com/toyobayashi/node-addon-examples)
29
+
30
+ ## Prerequests
31
+
32
+ You will need to install:
33
+
34
+ - Node.js `>= v16.15.0`
35
+ - npm `>= v8`
36
+ - Emscripten `>= v3.1.9` / wasi-sdk / LLVM clang with wasm support
37
+ - (Optional) CMake `>= v3.13`
38
+ - (Optional) ninja
39
+ - (Optional) make
40
+
41
+ There are several choices to get `make` for Windows user
42
+
43
+ - Install [mingw-w64](https://www.mingw-w64.org/downloads/), then use `mingw32-make`
44
+ - Download [MSVC prebuilt binary of GNU make](https://github.com/toyobayashi/make-win-build/releases), add to `%Path%` then rename it to `mingw32-make`
45
+ - Install [Visual Studio 2022](https://visualstudio.microsoft.com/) C++ desktop workload, use `nmake` in `Visual Studio Developer Command Prompt`
46
+ - Install [Visual C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/), use `nmake` in `Visual Studio Developer Command Prompt`
47
+
48
+ Verify your environment:
49
+
50
+ ```bash
51
+ node -v
52
+ npm -v
53
+ emcc -v
54
+
55
+ # clang -v
56
+ # clang -print-targets # ensure wasm32 target exists
57
+
58
+ cmake --version
59
+
60
+ # if you use ninja
61
+ ninja --version
62
+
63
+ # if you use make
64
+ make -v
65
+
66
+ # if you use nmake in Visual Studio Developer Command Prompt
67
+ nmake /?
68
+ ```
69
+
70
+ ## Build from source
71
+
72
+ You need to set `EMSDK` and `WASI_SDK_PATH` environment variables.
73
+
74
+ ```bash
75
+ git clone https://github.com/toyobayashi/emnapi.git
76
+ cd ./emnapi
77
+ npm install -g node-gyp
78
+ npm install
79
+ npm run build # output ./packages/*/dist
80
+ node ./script/release.js # output ./out
81
+
82
+ # test
83
+ npm run rebuild:test
84
+ npm test
85
+ ```
86
+
87
+ See [CONTRIBUTING](https://github.com/toyobayashi/emnapi/blob/main/CONTRIBUTING.md) for more details.
88
+
89
+ ## Quick Start
90
+
91
+ ### NPM Install
92
+
93
+ ```bash
94
+ npm install -D emnapi
95
+ npm install @emnapi/runtime
96
+
97
+ # for non-emscripten
98
+ npm install @emnapi/core
99
+ ```
100
+
101
+ Each package should match the same version.
102
+
103
+ ### Using C
104
+
105
+ Create `hello.c`.
106
+
107
+ ```c
108
+ #include <node_api.h>
109
+
110
+ #define NAPI_CALL(env, the_call) \
111
+ do { \
112
+ if ((the_call) != napi_ok) { \
113
+ const napi_extended_error_info *error_info; \
114
+ napi_get_last_error_info((env), &error_info); \
115
+ bool is_pending; \
116
+ const char* err_message = error_info->error_message; \
117
+ napi_is_exception_pending((env), &is_pending); \
118
+ if (!is_pending) { \
119
+ const char* error_message = err_message != NULL ? \
120
+ err_message : \
121
+ "empty error message"; \
122
+ napi_throw_error((env), NULL, error_message); \
123
+ } \
124
+ return NULL; \
125
+ } \
126
+ } while (0)
127
+
128
+ static napi_value js_hello(napi_env env, napi_callback_info info) {
129
+ napi_value world;
130
+ const char* str = "world";
131
+ NAPI_CALL(env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &world));
132
+ return world;
133
+ }
134
+
135
+ NAPI_MODULE_INIT() {
136
+ napi_value hello;
137
+ NAPI_CALL(env, napi_create_function(env, "hello", NAPI_AUTO_LENGTH,
138
+ js_hello, NULL, &hello));
139
+ NAPI_CALL(env, napi_set_named_property(env, exports, "hello", hello));
140
+ return exports;
141
+ }
142
+ ```
143
+
144
+ The C code is equivalant to the following JavaScript:
145
+
146
+ ```js
147
+ module.exports = (function (exports) {
148
+ const hello = function hello () {
149
+ // native code in js_hello
150
+ const world = 'world'
151
+ return world
152
+ }
153
+
154
+ exports.hello = hello
155
+ return exports
156
+ })(module.exports)
157
+ ```
158
+
159
+ #### Building
160
+
161
+ <details>
162
+ <summary>emscripten</summary><br />
163
+
164
+ ```bash
165
+ emcc -O3 \
166
+ -I./node_modules/emnapi/include \
167
+ -L./node_modules/emnapi/lib/wasm32-emscripten \
168
+ --js-library=./node_modules/emnapi/dist/library_napi.js \
169
+ -sEXPORTED_FUNCTIONS="['_malloc','_free']" \
170
+ -o hello.js \
171
+ hello.c \
172
+ -lemnapi
173
+ ```
174
+
175
+ </details>
176
+
177
+ <details>
178
+ <summary>wasi-sdk</summary><br />
179
+
180
+ ```bash
181
+ clang -O3 \
182
+ -I./node_modules/emnapi/include \
183
+ -L./node_modules/emnapi/lib/wasm32-wasi \
184
+ --target=wasm32-wasi \
185
+ --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
186
+ -mexec-model=reactor \
187
+ -Wl,--initial-memory=16777216 \
188
+ -Wl,--export-dynamic \
189
+ -Wl,--export=malloc \
190
+ -Wl,--export=free \
191
+ -Wl,--export=napi_register_wasm_v1 \
192
+ -Wl,--import-undefined \
193
+ -Wl,--export-table \
194
+ -o hello.wasm \
195
+ hello.c \
196
+ -lemnapi
197
+ ```
198
+
199
+ </details>
200
+
201
+ <details>
202
+ <summary>clang wasm32</summary><br />
203
+
204
+ Choose `libdlmalloc.a` or `libemmalloc.a` for `malloc` and `free`.
205
+
206
+ ```bash
207
+ clang -O3 \
208
+ -I./node_modules/emnapi/include \
209
+ -L./node_modules/emnapi/lib/wasm32 \
210
+ --target=wasm32 \
211
+ -nostdlib \
212
+ -Wl,--no-entry \
213
+ -Wl,--initial-memory=16777216 \
214
+ -Wl,--export-dynamic \
215
+ -Wl,--export=malloc \
216
+ -Wl,--export=free \
217
+ -Wl,--export=napi_register_wasm_v1 \
218
+ -Wl,--import-undefined \
219
+ -Wl,--export-table \
220
+ -o hello.wasm \
221
+ hello.c \
222
+ -lemnapi \
223
+ -ldlmalloc # -lemmalloc
224
+ ```
225
+
226
+ </details>
227
+
228
+ #### Initialization
229
+
230
+ To initialize emnapi, you need to import the emnapi runtime to create a `Context` by `createContext` or `getDefaultContext` first.
231
+ Each context owns isolated Node-API object such as `napi_env`, `napi_value`, `napi_ref`. If you have multiple emnapi modules, you should reuse the same `Context` across them.
232
+
233
+ ```ts
234
+ declare namespace emnapi {
235
+ // module '@emnapi/runtime'
236
+ export class Context { /* ... */ }
237
+ /** Create a new context */
238
+ export function createContext (): Context
239
+ /** Create or get */
240
+ export function getDefaultContext (): Context
241
+ // ...
242
+ }
243
+ ```
244
+
245
+ <details>
246
+ <summary>emscripten</summary><br />
247
+
248
+ then call `Module.emnapiInit` after emscripten runtime initialized.
249
+ `Module.emnapiInit` only do initialization once, it will always return the same binding exports after successfully initialized.
250
+
251
+ ```ts
252
+ declare namespace Module {
253
+ interface EmnapiInitOptions {
254
+ context: emnapi.Context
255
+
256
+ /** node_api_get_module_file_name */
257
+ filename?: string
258
+
259
+ /**
260
+ * Support following async_hooks related things
261
+ * on Node.js runtime only
262
+ *
263
+ * napi_async_init,
264
+ * napi_async_destroy,
265
+ * napi_make_callback,
266
+ * async resource parameter of
267
+ * napi_create_async_work and napi_create_threadsafe_function
268
+ */
269
+ nodeBinding?: typeof import('@emnapi/node-binding')
270
+ }
271
+ export function emnapiInit (options: EmnapiInitOptions): any
272
+ }
273
+ ```
274
+
275
+ ```html
276
+ <script src="node_modules/@emnapi/runtime/dist/emnapi.min.js"></script>
277
+ <script src="hello.js"></script>
278
+ <script>
279
+ Module.onRuntimeInitialized = function () {
280
+ var binding;
281
+ try {
282
+ binding = Module.emnapiInit({ context: emnapi.getDefaultContext() });
283
+ } catch (err) {
284
+ console.error(err);
285
+ return;
286
+ }
287
+ var msg = 'hello ' + binding.hello();
288
+ window.alert(msg);
289
+ };
290
+
291
+ // if -sMODULARIZE=1
292
+ Module({ /* Emscripten module init options */ }).then(function (Module) {
293
+ var binding = Module.emnapiInit({ context: emnapi.getDefaultContext() });
294
+ });
295
+ </script>
296
+ ```
297
+
298
+ If you are using `Visual Studio Code` and have `Live Server` extension installed, you can right click the HTML file in Visual Studio Code source tree and click `Open With Live Server`, then you can see the hello world alert!
299
+
300
+ Running on Node.js:
301
+
302
+ ```js
303
+ const emnapi = require('@emnapi/runtime')
304
+ const Module = require('./hello.js')
305
+
306
+ Module.onRuntimeInitialized = function () {
307
+ let binding
308
+ try {
309
+ binding = Module.emnapiInit({ context: emnapi.getDefaultContext() })
310
+ } catch (err) {
311
+ console.error(err)
312
+ return
313
+ }
314
+ const msg = `hello ${binding.hello()}`
315
+ console.log(msg)
316
+ }
317
+
318
+ // if -sMODULARIZE=1
319
+ Module({ /* Emscripten module init options */ }).then((Module) => {
320
+ const binding = Module.emnapiInit({ context: emnapi.getDefaultContext() })
321
+ })
322
+ ```
323
+
324
+ </details>
325
+
326
+ <details>
327
+ <summary>wasi-sdk or clang wasm32</summary><br />
328
+
329
+ For non-emscripten, you need to use `@emnapi/core`. The initialization is similar to emscripten.
330
+
331
+ ```html
332
+ <script src="node_modules/@emnapi/runtime/dist/emnapi.min.js"></script>
333
+ <script src="node_modules/@emnapi/core/dist/emnapi-core.min.js"></script>
334
+ <script>
335
+ const napiModule = emnapiCore.createNapiModule({
336
+ context: emnapi.getDefaultContext()
337
+ })
338
+
339
+ fetch('./hello.wasm').then(res => res.arrayBuffer()).then(wasmBuffer => {
340
+ return WebAssembly.instantiate(wasmBuffer, {
341
+ env: {
342
+ ...napiModule.imports.env,
343
+ // Currently napi-rs imports all symbols from env module
344
+ ...napiModule.imports.napi,
345
+ ...napiModule.imports.emnapi
346
+ },
347
+ // clang
348
+ napi: napiModule.imports.napi,
349
+ emnapi: napiModule.imports.emnapi
350
+ })
351
+ }).then(({ instance }) => {
352
+ const binding = napiModule.init(
353
+ instance, // WebAssembly.Instance
354
+ instance.exports.memory, // WebAssembly.Memory
355
+ instance.exports.__indirect_function_table // WebAssembly.Table
356
+ )
357
+ // binding === napiModule.exports
358
+ })
359
+ </script>
360
+ ```
361
+
362
+ Using WASI on Node.js
363
+
364
+ ```js
365
+ const { createNapiModule } = require('@emnapi/core')
366
+ const { getDefaultContext } = require('@emnapi/runtime')
367
+ const { WASI } = require('wasi')
368
+
369
+ const napiModule = createNapiModule({
370
+ context: getDefaultContext()
371
+ })
372
+
373
+ const wasi = new WASI({ /* ... */ })
374
+
375
+ WebAssembly.instantiate(require('fs').readFileSync('./hello.wasm'), {
376
+ wasi_snapshot_preview1: wasi.wasiImport,
377
+ env: {
378
+ ...napiModule.imports.env,
379
+ // Currently napi-rs imports all symbols from env module
380
+ ...napiModule.imports.napi,
381
+ ...napiModule.imports.emnapi
382
+ },
383
+ // clang
384
+ napi: napiModule.imports.napi,
385
+ emnapi: napiModule.imports.emnapi
386
+ }).then(({ instance }) => {
387
+ wasi.initialize(instance)
388
+ const binding = napiModule.init(
389
+ instance,
390
+ instance.exports.memory,
391
+ instance.exports.__indirect_function_table
392
+ )
393
+ // binding === napiModule.exports
394
+ })
395
+ ```
396
+
397
+ Using WASI on browser, you can use WASI polyfill in [wasm-util](https://github.com/toyobayashi/wasm-util),
398
+ and [memfs-browser](https://github.com/toyobayashi/memfs-browser)
399
+
400
+ ```js
401
+ import { createNapiModule } from '@emnapi/core'
402
+ import { getDefaultContext } from '@emnapi/runtime'
403
+ import { WASI } from '@tybys/wasm-util'
404
+ import { Volumn, createFsFromVolume } from 'memfs-browser'
405
+
406
+ const napiModule = createNapiModule({
407
+ context: getDefaultContext()
408
+ })
409
+
410
+ const fs = createFsFromVolume(Volume.from({ /* ... */ }))
411
+ const wasi = WASI.createSync({ fs, /* ... */ })
412
+
413
+ WebAssembly.instantiate(wasmBuffer, {
414
+ wasi_snapshot_preview1: wasi.wasiImport,
415
+ env: {
416
+ ...napiModule.imports.env,
417
+ // Currently napi-rs imports all symbols from env module
418
+ ...napiModule.imports.napi,
419
+ ...napiModule.imports.emnapi
420
+ },
421
+ // clang
422
+ napi: napiModule.imports.napi,
423
+ emnapi: napiModule.imports.emnapi
424
+ }).then(({ instance }) => {
425
+ wasi.initialize(instance)
426
+ const binding = napiModule.init(
427
+ instance,
428
+ instance.exports.memory,
429
+ instance.exports.__indirect_function_table
430
+ )
431
+ // binding === napiModule.exports
432
+ })
433
+ ```
434
+
435
+ </details>
436
+
437
+ ### Using C++
438
+
439
+ Alternatively, you can also use [`node-addon-api`](https://github.com/nodejs/node-addon-api) which is official Node-API C++ wrapper, already shipped ([v6.0.0](https://github.com/nodejs/node-addon-api/releases/tag/v6.0.0)) in this package but without Node.js specific API such as `CallbackScope`.
440
+
441
+ **Note: C++ wrapper can only be used to target Node.js v14.6.0+ and modern browsers those support `FinalizationRegistry` and `WeakRef` ([v8 engine v8.4+](https://v8.dev/blog/v8-release-84))!**
442
+
443
+ Create `hello.cpp`.
444
+
445
+ ```cpp
446
+ #include <napi.h>
447
+
448
+ Napi::String Method(const Napi::CallbackInfo& info) {
449
+ Napi::Env env = info.Env();
450
+ return Napi::String::New(env, "world");
451
+ }
452
+
453
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
454
+ exports.Set(Napi::String::New(env, "hello"),
455
+ Napi::Function::New(env, Method)).Check();
456
+ return exports;
457
+ }
458
+
459
+ NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
460
+ ```
461
+
462
+ Compile `hello.cpp` using `em++`. C++ exception is disabled by Emscripten default, and not supported by wasi-sdk, so predefine `-DNAPI_DISABLE_CPP_EXCEPTIONS` and `-DNODE_ADDON_API_ENABLE_MAYBE` here. If you would like to enable C++ exception, use `-sDISABLE_EXCEPTION_CATCHING=0` instead and remove `.Check()` call. See official documentation [here](https://github.com/nodejs/node-addon-api/blob/main/doc/error_handling.md).
463
+
464
+ #### Building
465
+
466
+ <details>
467
+ <summary>emscripten</summary><br />
468
+
469
+ ```bash
470
+ em++ -O3 \
471
+ -DNAPI_DISABLE_CPP_EXCEPTIONS \
472
+ -DNODE_ADDON_API_ENABLE_MAYBE \
473
+ -I./node_modules/emnapi/include \
474
+ -L./node_modules/emnapi/lib/wasm32-emscripten \
475
+ --js-library=./node_modules/emnapi/dist/library_napi.js \
476
+ -sEXPORTED_FUNCTIONS="['_malloc','_free']" \
477
+ -o hello.js \
478
+ hello.cpp \
479
+ -lemnapi
480
+ ```
481
+
482
+ </details>
483
+
484
+ <details>
485
+ <summary>wasi-sdk</summary><br />
486
+
487
+ ```bash
488
+ clang++ -O3 \
489
+ -DNAPI_DISABLE_CPP_EXCEPTIONS \
490
+ -DNODE_ADDON_API_ENABLE_MAYBE \
491
+ -I./node_modules/emnapi/include \
492
+ -L./node_modules/emnapi/lib/wasm32-wasi \
493
+ --target=wasm32-wasi \
494
+ --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
495
+ -mexec-model=reactor \
496
+ -Wl,--initial-memory=16777216 \
497
+ -Wl,--export-dynamic \
498
+ -Wl,--export=malloc \
499
+ -Wl,--export=free \
500
+ -Wl,--export=napi_register_wasm_v1 \
501
+ -Wl,--import-undefined \
502
+ -Wl,--export-table \
503
+ -o hello.wasm \
504
+ hello.cpp \
505
+ -lemnapi
506
+ ```
507
+
508
+ </details>
509
+
510
+ <details>
511
+ <summary>clang wasm32</summary><br />
512
+
513
+ `node-addon-api` is using the C++ standard libraries, so you must use WASI if you are using `node-addon-api`.
514
+
515
+ You can still use `wasm32-unknown-unknown` target if you use Node-API C API only in C++.
516
+
517
+ ```bash
518
+ clang++ -O3 \
519
+ -I./node_modules/emnapi/include \
520
+ -L./node_modules/emnapi/lib/wasm32 \
521
+ --target=wasm32 \
522
+ -nostdlib \
523
+ -Wl,--no-entry \
524
+ -Wl,--initial-memory=16777216 \
525
+ -Wl,--export-dynamic \
526
+ -Wl,--export=malloc \
527
+ -Wl,--export=free \
528
+ -Wl,--export=napi_register_wasm_v1 \
529
+ -Wl,--import-undefined \
530
+ -Wl,--export-table \
531
+ -o node_api_c_api_only.wasm \
532
+ node_api_c_api_only.cpp \
533
+ -lemnapi \
534
+ -ldlmalloc # -lemmalloc
535
+ ```
536
+
537
+ `operator new` and `operator delete`.
538
+
539
+ ```cpp
540
+ #include <stddef.h>
541
+
542
+ extern "C" void* malloc(size_t size);
543
+ extern "C" void free(void* p);
544
+
545
+ void* operator new(size_t size) {
546
+ return malloc(size);
547
+ }
548
+
549
+ void operator delete(void* p) noexcept {
550
+ free(p);
551
+ }
552
+ ```
553
+
554
+ </details>
555
+
556
+ ### Using CMake
557
+
558
+ Create `CMakeLists.txt`.
559
+
560
+ ```cmake
561
+ cmake_minimum_required(VERSION 3.13)
562
+
563
+ project(emnapiexample)
564
+
565
+ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/emnapi")
566
+
567
+ add_executable(hello hello.c)
568
+
569
+ target_link_libraries(hello emnapi)
570
+ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
571
+ target_link_options(hello PRIVATE
572
+ "-sEXPORTED_FUNCTIONS=['_malloc','_free']"
573
+ )
574
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "WASI")
575
+ target_link_options(hello PRIVATE
576
+ "-mexec-model=reactor"
577
+ "-Wl,--export=napi_register_wasm_v1"
578
+ "-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table"
579
+ )
580
+ elseif((CMAKE_C_COMPILER_TARGET STREQUAL "wasm32") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-unknown-unknown"))
581
+ target_link_options(hello PRIVATE
582
+ "-nostdlib"
583
+ "-Wl,--export=napi_register_wasm_v1"
584
+ "-Wl,--no-entry"
585
+ "-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table"
586
+ )
587
+ target_link_libraries(hello dlmalloc)
588
+ # target_link_libraries(hello emmalloc)
589
+ endif()
590
+ ```
591
+
592
+ ```bash
593
+ mkdir build
594
+
595
+ # emscripten
596
+ emcmake cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -H. -Bbuild
597
+
598
+ # wasi-sdk
599
+ cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake \
600
+ -DWASI_SDK_PREFIX=$WASI_SDK_PATH \
601
+ -DCMAKE_BUILD_TYPE=Release \
602
+ -G Ninja -H. -Bbuild
603
+
604
+ # wasm32
605
+ cmake -DCMAKE_TOOLCHAIN_FILE=node_modules/emnapi/cmake/wasm32.cmake \
606
+ -DLLVM_PREFIX=$WASI_SDK_PATH \
607
+ -DCMAKE_BUILD_TYPE=Release \
608
+ -G Ninja -H. -Bbuild
609
+
610
+ cmake --build build
611
+ ```
612
+
613
+ Output code can run in recent version modern browsers and Node.js latest LTS. IE is not supported.
614
+
615
+ ### Using Rust (Experimental)
616
+
617
+ Currently you can use [napi-rs](https://github.com/napi-rs/napi-rs) like this, more work is working in progress.
618
+
619
+ Note: WASI target require rust nightly toolchain.
620
+
621
+ <details>
622
+ <summary>Cargo.toml</summary><br />
623
+
624
+ ```toml
625
+ [package]
626
+ edition = "2021"
627
+ name = "binding"
628
+ version = "0.0.0"
629
+
630
+ # We should build binary for WASI reactor
631
+ # https://github.com/rust-lang/rust/pull/79997
632
+ # https://github.com/WebAssembly/WASI/issues/24
633
+ # for wasm
634
+ [[bin]]
635
+ name = "binding"
636
+ path = "src/main.rs"
637
+
638
+ # for native
639
+ # [lib]
640
+ # name = "binding"
641
+ # path = "src/lib.rs"
642
+ # crate-type = ["cdylib"]
643
+
644
+ [dependencies]
645
+ napi = { version = "2.10.13", default-features = false, features = ["napi8", "compat-mode"] }
646
+ napi-sys = { version = "2.2.3", features = ["napi8"] }
647
+ napi-derive = "2.10.0"
648
+
649
+ [build-dependencies]
650
+ napi-build = "2.0.1"
651
+
652
+ [profile.release]
653
+ strip = "symbols"
654
+ ```
655
+
656
+ </details>
657
+
658
+ <details>
659
+ <summary>.cargo/config.toml</summary><br />
660
+
661
+ ```toml
662
+ [build]
663
+ target = [
664
+ "wasm32-unknown-unknown",
665
+ "wasm32-wasi"
666
+ ]
667
+
668
+ [target.wasm32-unknown-unknown]
669
+ rustflags = [
670
+ "-L./node_modules/emnapi/lib/wasm32",
671
+ "-lemnapi",
672
+ "-ldlmalloc",
673
+ # "-lemmalloc",
674
+ "-C", "link-arg=--no-entry",
675
+ "-C", "link-arg=--initial-memory=16777216",
676
+ "-C", "link-arg=--export-dynamic",
677
+ "-C", "link-arg=--export=malloc",
678
+ "-C", "link-arg=--export=free",
679
+ "-C", "link-arg=--export=napi_register_wasm_v1",
680
+ "-C", "link-arg=--export-table",
681
+ "-C", "link-arg=--import-undefined",
682
+ ]
683
+
684
+ [target.wasm32-wasi]
685
+ rustflags = [
686
+ "-L./node_modules/emnapi/lib/wasm32-wasi",
687
+ "-lemnapi",
688
+ "-C", "link-arg=--initial-memory=16777216",
689
+ "-C", "link-arg=--export-dynamic",
690
+ "-C", "link-arg=--export=malloc",
691
+ "-C", "link-arg=--export=free",
692
+ "-C", "link-arg=--export=napi_register_wasm_v1",
693
+ "-C", "link-arg=--export-table",
694
+ "-C", "link-arg=--import-undefined",
695
+ "-Z", "wasi-exec-model=reactor", # +nightly
696
+ ]
697
+ ```
698
+
699
+ </details>
700
+
701
+ <details>
702
+ <summary>src/main.rs</summary><br />
703
+
704
+ ```rust
705
+ #![no_main]
706
+
707
+ use napi::*;
708
+
709
+ #[cfg(target_arch = "wasm32")]
710
+ use napi::bindgen_prelude::*;
711
+ #[cfg(target_arch = "wasm32")]
712
+ use napi_sys::*;
713
+
714
+ #[macro_use]
715
+ extern crate napi_derive;
716
+
717
+ fn sum(a: i32, b: i32) -> i32 {
718
+ a + b
719
+ }
720
+
721
+ #[js_function(2)]
722
+ fn sum_js(ctx: CallContext) -> napi::Result<napi::JsNumber> {
723
+ let arg0 = ctx.get::<napi::JsNumber>(0)?.get_int32()?;
724
+ let arg1 = ctx.get::<napi::JsNumber>(1)?.get_int32()?;
725
+ let ret = sum(arg0, arg1);
726
+ ctx.env.create_int32(ret)
727
+ }
728
+
729
+ fn module_register(_env: napi::Env, mut exports: napi::JsObject) -> napi::Result<()> {
730
+ exports.create_named_method("sum", sum_js)?;
731
+
732
+ Ok(())
733
+ }
734
+
735
+ #[cfg(not(target_arch = "wasm32"))]
736
+ #[module_exports]
737
+ fn init(exports: napi::JsObject, env: napi::Env) -> napi::Result<()> {
738
+ module_register(env, exports)
739
+ }
740
+
741
+ #[cfg(target_arch = "wasm32")]
742
+ #[no_mangle]
743
+ pub unsafe extern "C" fn napi_register_wasm_v1(env: napi_env, exports: napi_value) -> () {
744
+ let env_object = napi::Env::from_raw(env);
745
+ let exports_object = napi::JsObject::from_napi_value(env, exports).unwrap();
746
+ module_register(env_object, exports_object).unwrap();
747
+ }
748
+ ```
749
+
750
+ </details>
751
+
752
+ ### Multithread (Emscripten Only)
753
+
754
+ If you want to use async work or thread safe functions,
755
+ there are additional C source file need to be compiled and linking.
756
+ Recommend use CMake directly.
757
+
758
+ ```cmake
759
+ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/emnapi")
760
+
761
+ add_executable(hello hello.c)
762
+
763
+ target_link_libraries(hello emnapi-mt)
764
+ target_compile_options(hello PRIVATE "-sUSE_PTHREADS=1")
765
+ target_link_options(hello PRIVATE
766
+ "-sALLOW_MEMORY_GROWTH=1"
767
+ "-sEXPORTED_FUNCTIONS=['_malloc','_free']"
768
+ "-sUSE_PTHREADS=1"
769
+ "-sPTHREAD_POOL_SIZE=4"
770
+ # try to specify stack size if you experience pthread errors
771
+ "-sSTACK_SIZE=2MB"
772
+ "-sDEFAULT_PTHREAD_STACK_SIZE=2MB"
773
+ )
774
+ ```
775
+
776
+ ```bash
777
+ emcmake cmake -DCMAKE_BUILD_TYPE=Release -DEMNAPI_WORKER_POOL_SIZE=4 -G Ninja -H. -Bbuild
778
+ cmake --build build
779
+ ```
780
+
781
+ ## Preprocess Macro Options
782
+
783
+ ### `-DEMNAPI_WORKER_POOL_SIZE=4`
784
+
785
+ This is [`UV_THREADPOOL_SIZE`](http://docs.libuv.org/en/v1.x/threadpool.html?highlight=UV_THREADPOOL_SIZE) equivalent at compile time, if not predefined, emnapi will read `UV_THREADPOOL_SIZE` from Emscripten [environment variable](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-environment-variables) at runtime, you can set `UV_THREADPOOL_SIZE` like this:
786
+
787
+ ```js
788
+ Module.preRun = Module.preRun || [];
789
+ Module.preRun.push(function () {
790
+ if (typeof ENV !== 'undefined') {
791
+ ENV.UV_THREADPOOL_SIZE = '2';
792
+ }
793
+ });
794
+ ```
795
+
796
+ It represent max of `EMNAPI_WORKER_POOL_SIZE` async work (`napi_queue_async_work`) can be executed in parallel. Default is not defined, read `UV_THREADPOOL_SIZE` at runtime.
797
+
798
+ You can set both `PTHREAD_POOL_SIZE` and `EMNAPI_WORKER_POOL_SIZE` to `number of CPU cores` in general.
799
+ If you use another library function which may create `N` child threads in async work,
800
+ then you need to set `PTHREAD_POOL_SIZE` to `EMNAPI_WORKER_POOL_SIZE * (N + 1)`.
801
+
802
+ This option only has effect if you use `-sUSE_PTHREADS`.
803
+ Emnapi will create `EMNAPI_WORKER_POOL_SIZE` threads when initializing,
804
+ it will throw error if `PTHREAD_POOL_SIZE < EMNAPI_WORKER_POOL_SIZE && PTHREAD_POOL_SIZE_STRICT == 2`.
805
+
806
+ See [Issue #8](https://github.com/toyobayashi/emnapi/issues/8) for more detail.
807
+
808
+ ### `-DEMNAPI_NEXTTICK_TYPE=0`
809
+
810
+ This option only has effect if you use `-sUSE_PTHREADS`, Default is `0`.
811
+ Tell emnapi how to delay async work in `uv_async_send` / `uv__async_close`.
812
+
813
+ - `0`: Use `setImmediate()` (Node.js native `setImmediate` or browser `MessageChannel` and `port.postMessage`)
814
+ - `1`: Use `Promise.resolve().then()`
815
+
816
+ ### `-DEMNAPI_USE_PROXYING=1`
817
+
818
+ This option only has effect if you use `-sUSE_PTHREADS`. Default is `1` if emscripten version `>= 3.1.9`, else `0`.
819
+
820
+ - `0`
821
+
822
+ Use JavaScript implementation to send async work from worker threads, runtime code will access the Emscripten internal `PThread` object to add custom worker message listener.
823
+
824
+ - `1`:
825
+
826
+ Use Emscripten [proxying API](https://emscripten.org/docs/api_reference/proxying.h.html) to send async work from worker threads in C. If you experience something wrong, you can switch set this to `0` and feel free to create an issue.
827
+
828
+ ## Performance compare with Embind
829
+
830
+ See source code [here](https://github.com/toyobayashi/emnapi/tree/main/packages/bench)
831
+
832
+ - OS: Windows_NT x64 10.0.22621.963
833
+ - CPU: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz 2.30 GHz
834
+ - Memory: 16.0 GB
835
+ - Chrome: 108.0.5359.125
836
+ - Emscripten: 3.1.28
837
+ - emnapi: 0.22.0
838
+
839
+ ```
840
+ binding: function () {}
841
+ embind #emptyFunction x 37,148,158 ops/sec ±0.39% (67 runs sampled)
842
+ emnapi #emptyFunction x 40,207,668 ops/sec ±0.79% (67 runs sampled)
843
+ node-addon-api + emnapi #emptyFunction x 13,871,523 ops/sec ±0.35% (67 runs sampled)
844
+ Fastest is emnapi #emptyFunction
845
+
846
+ binding: function (obj) { return obj }
847
+ embind #returnParam x 19,230,099 ops/sec ±0.59% (68 runs sampled)
848
+ emnapi #returnParam x 14,930,264 ops/sec ±0.68% (64 runs sampled)
849
+ node-addon-api + emnapi #returnParam x 11,402,133 ops/sec ±0.47% (67 runs sampled)
850
+ Fastest is embind #returnParam
851
+
852
+ binding: function (int) { return copy(int) }
853
+ embind #convertInteger x 9,178,287 ops/sec ±0.67% (67 runs sampled)
854
+ emnapi #convertInteger x 9,898,681 ops/sec ±0.56% (66 runs sampled)
855
+ node-addon-api + emnapi #convertInteger x 7,888,491 ops/sec ±0.79% (67 runs sampled)
856
+ Fastest is emnapi #convertInteger
857
+
858
+ binding: function (str) { return copy(str) }
859
+ embind #convertString x 2,574,960 ops/sec ±0.46% (66 runs sampled)
860
+ emnapi #convertString x 3,412,941 ops/sec ±0.66% (68 runs sampled)
861
+ node-addon-api + emnapi #convertString x 2,913,797 ops/sec ±0.62% (67 runs sampled)
862
+ Fastest is emnapi #convertString
863
+
864
+ binding: function (param) { return param.length }
865
+ embind #ObjectGet x 6,192,531 ops/sec ±0.73% (67 runs sampled)
866
+ emnapi #ObjectGet x 5,268,653 ops/sec ±0.59% (66 runs sampled)
867
+ node-addon-api + emnapi #ObjectGet x 4,828,204 ops/sec ±0.57% (67 runs sampled)
868
+ Fastest is embind #ObjectGet
869
+
870
+ binding: function (obj, key, value) { obj[key] = value }
871
+ embind #ObjectSet x 11,467,433 ops/sec ±0.55% (67 runs sampled)
872
+ emnapi #ObjectSet x 9,476,512 ops/sec ±0.91% (66 runs sampled)
873
+ node-addon-api + emnapi #ObjectSet x 7,647,341 ops/sec ±0.36% (66 runs sampled)
874
+ Fastest is embind #ObjectSet
875
+ ```