emnapi 0.31.0 → 0.33.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.
package/CMakeLists.txt CHANGED
@@ -26,7 +26,19 @@ set(UV_SRC
26
26
  "${CMAKE_CURRENT_SOURCE_DIR}/src/uv/unix/core.c"
27
27
  )
28
28
 
29
- set(EMNAPI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/emnapi.c")
29
+ set(ENAPI_BASIC_SRC
30
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/js_native_api.c"
31
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/node_api.c"
32
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/emnapi.c"
33
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/async_cleanup_hook.c"
34
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/async_context.c"
35
+ )
36
+ set(EMNAPI_THREADS_SRC
37
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/async_work.c"
38
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/threadsafe_function.c"
39
+ )
40
+ set(EMNAPI_SRC ${ENAPI_BASIC_SRC} ${EMNAPI_THREADS_SRC})
41
+
30
42
  set(EMNAPI_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/include")
31
43
 
32
44
  set(EMNAPI_JS_LIB "${CMAKE_CURRENT_SOURCE_DIR}/dist/library_napi.js")
@@ -37,6 +49,7 @@ else()
37
49
  set(EMNAPI_MT_CFLAGS "-pthread")
38
50
  endif()
39
51
 
52
+ set(EMNAPI_BASIC_TARGET_NAME "emnapi-basic")
40
53
  set(EMNAPI_TARGET_NAME "emnapi")
41
54
  set(EMNAPI_MT_TARGET_NAME "emnapi-mt")
42
55
  set(DLMALLOC_TARGET_NAME "dlmalloc")
@@ -82,6 +95,12 @@ if(IS_EMSCRIPTEN)
82
95
  target_link_options(${EMNAPI_TARGET_NAME} INTERFACE "--js-library=${EMNAPI_JS_LIB}")
83
96
  endif()
84
97
 
98
+ add_library(${EMNAPI_BASIC_TARGET_NAME} STATIC ${ENAPI_BASIC_SRC})
99
+ target_include_directories(${EMNAPI_BASIC_TARGET_NAME} PUBLIC ${EMNAPI_INCLUDE})
100
+ if(IS_EMSCRIPTEN)
101
+ target_link_options(${EMNAPI_BASIC_TARGET_NAME} INTERFACE "--js-library=${EMNAPI_JS_LIB}")
102
+ endif()
103
+
85
104
  if(IS_EMSCRIPTEN OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads"))
86
105
  set(EMNAPI_BUILD_MT ON)
87
106
  else()
@@ -118,6 +137,7 @@ endif()
118
137
  # endif()
119
138
  if(LIB_ARCH)
120
139
  install(TARGETS ${EMNAPI_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
140
+ install(TARGETS ${EMNAPI_BASIC_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
121
141
  if(EMNAPI_BUILD_MT)
122
142
  install(TARGETS ${EMNAPI_MT_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
123
143
  endif()
@@ -128,7 +148,7 @@ if(LIB_ARCH)
128
148
  endif()
129
149
 
130
150
  install(FILES
131
- ${CMAKE_CURRENT_SOURCE_DIR}/include/common.h
151
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/emnapi_common.h
132
152
  ${CMAKE_CURRENT_SOURCE_DIR}/include/emnapi.h
133
153
  ${CMAKE_CURRENT_SOURCE_DIR}/include/js_native_api_types.h
134
154
  ${CMAKE_CURRENT_SOURCE_DIR}/include/js_native_api.h
@@ -150,6 +170,7 @@ install(FILES
150
170
  if(EMNAPI_INSTALL_SRC)
151
171
  install(FILES
152
172
  ${EMNAPI_SRC}
173
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/emnapi_internal.h"
153
174
  DESTINATION "src/${PROJECT_NAME}")
154
175
  install(DIRECTORY
155
176
  ${CMAKE_CURRENT_SOURCE_DIR}/src/uv
package/README.md CHANGED
@@ -6,9 +6,7 @@
6
6
 
7
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
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.
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 with wasm support. [napi-rs support is comming soon](https://github.com/napi-rs/napi-rs/tree/emnapi).
12
10
 
13
11
  This project aims to
14
12
 
@@ -27,6 +25,16 @@ See documentation for more details:
27
25
 
28
26
  [How to build Node-API official examples](https://github.com/toyobayashi/node-addon-examples)
29
27
 
28
+ Emscripten is the first class support target. If your target is running addon on browser,
29
+ we strongly recommend you to use Emscripten instead of wasi-sdk. Async works and threadsafe
30
+ functions related APIs are only available on Emscripten or `wasm32-wasi-threads` target since
31
+ they are relying on pthread. Though today we have [WASI browser polyfill](https://github.com/toyobayashi/wasm-util),
32
+ `wasm32-wasi-threads` is in very early stage and WASI itself is not designed for browser.
33
+ There are some limitations on browser about wasi-libc's pthread implementation, for example
34
+ `pthread_mutex_lock` may call `__builtin_wasm_memory_atomic_wait32`(`memory.atomic.wait32`)
35
+ which is disallowed in browser JS main thread. While Emscripten's pthread implementation
36
+ has considered usage in browser.
37
+
30
38
  ## Prerequests
31
39
 
32
40
  You will need to install:
@@ -332,29 +340,19 @@ For non-emscripten, you need to use `@emnapi/core`. The initialization is simila
332
340
  <script src="node_modules/@emnapi/runtime/dist/emnapi.min.js"></script>
333
341
  <script src="node_modules/@emnapi/core/dist/emnapi-core.min.js"></script>
334
342
  <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
343
+ emnapiCore.instantiateNapiModule(fetch('./hello.wasm'), {
344
+ context: emnapi.getDefaultContext(),
345
+ overwriteImports (importObject) {
346
+ // importObject.env = {
347
+ // ...importObject.env,
348
+ // ...importObject.napi,
349
+ // ...importObject.emnapi,
350
+ // // ...
351
+ // }
352
+ }
353
+ }).then(({ instance, module, napiModule }) => {
354
+ const binding = napiModule.exports
355
+ // ...
358
356
  })
359
357
  </script>
360
358
  ```
@@ -362,35 +360,25 @@ fetch('./hello.wasm').then(res => res.arrayBuffer()).then(wasmBuffer => {
362
360
  Using WASI on Node.js
363
361
 
364
362
  ```js
365
- const { createNapiModule } = require('@emnapi/core')
363
+ const { instantiateNapiModule } = require('@emnapi/core')
366
364
  const { getDefaultContext } = require('@emnapi/runtime')
367
365
  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
366
+ const fs = require('fs')
367
+
368
+ instantiateNapiModule(fs.promises.readFile('./hello.wasm'), {
369
+ wasi: new WASI({ /* ... */ }),
370
+ context: getDefaultContext(),
371
+ overwriteImports (importObject) {
372
+ // importObject.env = {
373
+ // ...importObject.env,
374
+ // ...importObject.napi,
375
+ // ...importObject.emnapi,
376
+ // // ...
377
+ // }
378
+ }
379
+ }).then(({ instance, module, napiModule }) => {
380
+ const binding = napiModule.exports
381
+ // ...
394
382
  })
395
383
  ```
396
384
 
@@ -398,37 +386,26 @@ Using WASI on browser, you can use WASI polyfill in [wasm-util](https://github.c
398
386
  and [memfs-browser](https://github.com/toyobayashi/memfs-browser)
399
387
 
400
388
  ```js
401
- import { createNapiModule } from '@emnapi/core'
389
+ import { instantiateNapiModule } from '@emnapi/core'
402
390
  import { getDefaultContext } from '@emnapi/runtime'
403
391
  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
392
+ import { Volume, createFsFromVolume } from 'memfs-browser'
393
+
394
+ const fs = createFsFromVolume(Volume.fromJSON({ /* ... */ }))
395
+ return instantiateNapiModule(fetch('./hello.wasm'), {
396
+ wasi: new WASI({ fs, /* ... */ })
397
+ context: getDefaultContext(),
398
+ overwriteImports (importObject) {
399
+ // importObject.env = {
400
+ // ...importObject.env,
401
+ // ...importObject.napi,
402
+ // ...importObject.emnapi,
403
+ // // ...
404
+ // }
405
+ }
406
+ }).then(({ instance, module, napiModule }) => {
407
+ const binding = napiModule.exports
408
+ // ...
432
409
  })
433
410
  ```
434
411
 
@@ -492,6 +469,7 @@ clang++ -O3 \
492
469
  -L./node_modules/emnapi/lib/wasm32-wasi \
493
470
  --target=wasm32-wasi \
494
471
  --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
472
+ -fno-exceptions \
495
473
  -mexec-model=reactor \
496
474
  -Wl,--initial-memory=16777216 \
497
475
  -Wl,--export-dynamic \
@@ -519,6 +497,7 @@ clang++ -O3 \
519
497
  -I./node_modules/emnapi/include \
520
498
  -L./node_modules/emnapi/lib/wasm32 \
521
499
  --target=wasm32 \
500
+ -fno-exceptions \
522
501
  -nostdlib \
523
502
  -Wl,--no-entry \
524
503
  -Wl,--initial-memory=16777216 \
@@ -749,35 +728,168 @@ pub unsafe extern "C" fn napi_register_wasm_v1(env: napi_env, exports: napi_valu
749
728
 
750
729
  </details>
751
730
 
752
- ### Multithread (Emscripten Only)
731
+ ### Multithread
753
732
 
754
733
  If you want to use async work or thread safe functions,
755
734
  there are additional C source file need to be compiled and linking.
756
735
  Recommend use CMake directly.
757
736
 
737
+ **This is EXPERIMENTAL on non-emscripten.**
738
+
758
739
  ```cmake
759
740
  add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/emnapi")
760
741
 
761
742
  add_executable(hello hello.c)
762
743
 
763
744
  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
- )
745
+
746
+ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
747
+ target_compile_options(hello PRIVATE "-sUSE_PTHREADS=1")
748
+ target_link_options(hello PRIVATE
749
+ "-sALLOW_MEMORY_GROWTH=1"
750
+ "-sEXPORTED_FUNCTIONS=['_malloc','_free']"
751
+ "-sUSE_PTHREADS=1"
752
+ "-sPTHREAD_POOL_SIZE=4"
753
+ # try to specify stack size if you experience pthread errors
754
+ "-sSTACK_SIZE=2MB"
755
+ "-sDEFAULT_PTHREAD_STACK_SIZE=2MB"
756
+ )
757
+ elseif(CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads")
758
+ # Experimental
759
+ target_compile_options(hello PRIVATE "-fno-exceptions" "-pthread")
760
+ target_link_options(hello PRIVATE
761
+ "-pthread"
762
+ "-mexec-model=reactor"
763
+ "-Wl,--import-memory"
764
+ "-Wl,--max-memory=2147483648"
765
+ "-Wl,--export-dynamic"
766
+ "-Wl,--export=malloc"
767
+ "-Wl,--export=free"
768
+ "-Wl,--import-undefined"
769
+ "-Wl,--export-table"
770
+ )
771
+ endif()
774
772
  ```
775
773
 
776
774
  ```bash
775
+ # emscripten
777
776
  emcmake cmake -DCMAKE_BUILD_TYPE=Release -DEMNAPI_WORKER_POOL_SIZE=4 -G Ninja -H. -Bbuild
777
+
778
+ # wasi-sdk with thread support (Experimental)
779
+ cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk-pthread.cmake \
780
+ -DWASI_SDK_PREFIX=$WASI_SDK_PATH \
781
+ -DCMAKE_BUILD_TYPE=Release \
782
+ -G Ninja -H. -Bbuild
783
+
778
784
  cmake --build build
779
785
  ```
780
786
 
787
+ And additional work is required during instantiating wasm compiled with non-emscripten.
788
+
789
+ ```js
790
+ // emnapi main thread (could be in a Worker)
791
+ instantiateNapiModule(input, {
792
+ context: getDefaultContext(),
793
+ wasi: new WASI(/* ... */),
794
+ // reuseWorker: true,
795
+ onCreateWorker () {
796
+ return new Worker('./worker.js')
797
+ // Node.js
798
+ // return new Worker(join(__dirname, './worker.js'), {
799
+ // env: process.env,
800
+ // execArgv: ['--experimental-wasi-unstable-preview1']
801
+ // })
802
+ },
803
+ overwriteImports (importObject) {
804
+ importObject.env.memory = new WebAssembly.Memory({
805
+ initial: 16777216 / 65536,
806
+ maximum: 2147483648 / 65536,
807
+ shared: true
808
+ })
809
+ }
810
+ })
811
+ ```
812
+
813
+ ```js
814
+ // worker.js
815
+ (function () {
816
+ let fs, WASI, emnapiCore
817
+
818
+ const ENVIRONMENT_IS_NODE =
819
+ typeof process === 'object' && process !== null &&
820
+ typeof process.versions === 'object' && process.versions !== null &&
821
+ typeof process.versions.node === 'string'
822
+
823
+ if (ENVIRONMENT_IS_NODE) {
824
+ const nodeWorkerThreads = require('worker_threads')
825
+
826
+ const parentPort = nodeWorkerThreads.parentPort
827
+
828
+ parentPort.on('message', (data) => {
829
+ globalThis.onmessage({ data })
830
+ })
831
+
832
+ fs = require('fs')
833
+
834
+ Object.assign(globalThis, {
835
+ self: globalThis,
836
+ require,
837
+ Worker: nodeWorkerThreads.Worker,
838
+ importScripts: function (f) {
839
+ (0, eval)(fs.readFileSync(f, 'utf8') + '//# sourceURL=' + f)
840
+ },
841
+ postMessage: function (msg) {
842
+ parentPort.postMessage(msg)
843
+ }
844
+ })
845
+
846
+ WASI = require('./wasi').WASI
847
+ emnapiCore = require('@emnapi/core')
848
+ } else {
849
+ importScripts('./node_modules/memfs-browser/dist/memfs.js')
850
+ importScripts('./node_modules/@tybys/wasm-util/dist/wasm-util.min.js')
851
+ importScripts('./node_modules/@emnapi/core/dist/emnapi-core.js')
852
+ emnapiCore = globalThis.emnapiCore
853
+
854
+ const { Volume, createFsFromVolume } = memfs
855
+ fs = createFsFromVolume(Volume.fromJSON({
856
+ '/': null
857
+ }))
858
+
859
+ WASI = globalThis.wasmUtil.WASI
860
+ }
861
+
862
+ const { instantiateNapiModuleSync, MessageHandler } = emnapiCore
863
+
864
+ const handler = new MessageHandler({
865
+ onLoad ({ wasmModule, wasmMemory }) {
866
+ const wasi = new WASI({
867
+ fs,
868
+ print: ENVIRONMENT_IS_NODE
869
+ ? (...args) => {
870
+ const str = require('util').format(...args)
871
+ fs.writeSync(1, str + '\n')
872
+ }
873
+ : function () { console.log.apply(console, arguments) }
874
+ })
875
+
876
+ return instantiateNapiModuleSync(wasmModule, {
877
+ childThread: true,
878
+ wasi,
879
+ overwriteImports (importObject) {
880
+ importObject.env.memory = wasmMemory
881
+ }
882
+ })
883
+ }
884
+ })
885
+
886
+ globalThis.onmessage = function (e) {
887
+ handler.handle(e)
888
+ // handle other messages
889
+ }
890
+ })()
891
+ ```
892
+
781
893
  ## Preprocess Macro Options
782
894
 
783
895
  ### `-DEMNAPI_WORKER_POOL_SIZE=4`
@@ -791,6 +903,13 @@ Module.preRun.push(function () {
791
903
  ENV.UV_THREADPOOL_SIZE = '2';
792
904
  }
793
905
  });
906
+
907
+ // wasi
908
+ new WASI({
909
+ env: {
910
+ UV_THREADPOOL_SIZE: '2'
911
+ }
912
+ })
794
913
  ```
795
914
 
796
915
  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.
@@ -815,7 +934,7 @@ Tell emnapi how to delay async work in `uv_async_send` / `uv__async_close`.
815
934
 
816
935
  ### `-DEMNAPI_USE_PROXYING=1`
817
936
 
818
- This option only has effect if you use `-sUSE_PTHREADS`. Default is `1` if emscripten version `>= 3.1.9`, else `0`.
937
+ This option only has effect if you use emscripten `-sUSE_PTHREADS`. Default is `1` if emscripten version `>= 3.1.9`, else `0`.
819
938
 
820
939
  - `0`
821
940