emnapi 0.34.0 → 0.35.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
@@ -1,6 +1,6 @@
1
1
  cmake_minimum_required(VERSION 3.13)
2
2
 
3
- project(emnapi)
3
+ project(emnapi LANGUAGES C ASM)
4
4
 
5
5
  option(EMNAPI_INSTALL_SRC "EMNAPI_INSTALL_SRC" OFF)
6
6
  option(EMNAPI_FIND_NODE_ADDON_API "EMNAPI_FIND_NODE_ADDON_API" OFF)
@@ -50,10 +50,13 @@ else()
50
50
  endif()
51
51
 
52
52
  set(EMNAPI_BASIC_TARGET_NAME "emnapi-basic")
53
+ set(EMNAPI_BASIC_MT_TARGET_NAME "emnapi-basic-mt")
53
54
  set(EMNAPI_TARGET_NAME "emnapi")
54
55
  set(EMNAPI_MT_TARGET_NAME "emnapi-mt")
55
56
  set(DLMALLOC_TARGET_NAME "dlmalloc")
57
+ set(DLMALLOC_MT_TARGET_NAME "dlmalloc-mt")
56
58
  set(EMMALLOC_TARGET_NAME "emmalloc")
59
+ set(EMMALLOC_MT_TARGET_NAME "emmalloc-mt")
57
60
 
58
61
  if(EMNAPI_FIND_NODE_ADDON_API)
59
62
  execute_process(
@@ -82,11 +85,26 @@ if(IS_WASM32)
82
85
  "${CMAKE_CURRENT_SOURCE_DIR}/src/malloc/dlmalloc/dlmalloc.c"
83
86
  )
84
87
  target_compile_definitions(${DLMALLOC_TARGET_NAME} PRIVATE "PAGESIZE=65536")
88
+
89
+ add_library(${DLMALLOC_MT_TARGET_NAME} STATIC
90
+ ${MALLOC_PUBLIC_SOURCES}
91
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/malloc/dlmalloc/dlmalloc.c"
92
+ )
93
+ target_compile_options(${DLMALLOC_MT_TARGET_NAME} PUBLIC "-matomics" "-mbulk-memory")
94
+ target_compile_definitions(${DLMALLOC_MT_TARGET_NAME} PRIVATE "PAGESIZE=65536" "USE_LOCKS=1")
95
+
85
96
  add_library(${EMMALLOC_TARGET_NAME} STATIC
86
97
  ${MALLOC_PUBLIC_SOURCES}
87
98
  "${CMAKE_CURRENT_SOURCE_DIR}/src/malloc/emmalloc/emmalloc.c"
88
99
  )
89
100
  target_compile_definitions(${EMMALLOC_TARGET_NAME} PRIVATE "PAGESIZE=65536")
101
+
102
+ add_library(${EMMALLOC_MT_TARGET_NAME} STATIC
103
+ ${MALLOC_PUBLIC_SOURCES}
104
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/malloc/emmalloc/emmalloc.c"
105
+ )
106
+ target_compile_options(${EMMALLOC_MT_TARGET_NAME} PUBLIC "-matomics" "-mbulk-memory")
107
+ target_compile_definitions(${EMMALLOC_MT_TARGET_NAME} PRIVATE "PAGESIZE=65536" "__EMSCRIPTEN_SHARED_MEMORY__=1")
90
108
  endif()
91
109
 
92
110
  add_library(${EMNAPI_TARGET_NAME} STATIC ${EMNAPI_SRC} ${UV_SRC})
@@ -101,6 +119,22 @@ if(IS_EMSCRIPTEN)
101
119
  target_link_options(${EMNAPI_BASIC_TARGET_NAME} INTERFACE "--js-library=${EMNAPI_JS_LIB}")
102
120
  endif()
103
121
 
122
+ if(IS_WASM32 OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads"))
123
+ set(EMNAPI_BUILD_BASIC_MT ON)
124
+ else()
125
+ set(EMNAPI_BUILD_BASIC_MT OFF)
126
+ endif()
127
+
128
+ if(EMNAPI_BUILD_BASIC_MT)
129
+ add_library(${EMNAPI_BASIC_MT_TARGET_NAME} STATIC
130
+ ${ENAPI_BASIC_SRC}
131
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/thread/async_worker_create.c"
132
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/thread/async_worker_init.S"
133
+ )
134
+ target_compile_options(${EMNAPI_BASIC_MT_TARGET_NAME} PUBLIC "-matomics" "-mbulk-memory")
135
+ target_include_directories(${EMNAPI_BASIC_MT_TARGET_NAME} PUBLIC ${EMNAPI_INCLUDE})
136
+ endif()
137
+
104
138
  if(IS_EMSCRIPTEN OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads"))
105
139
  set(EMNAPI_BUILD_MT ON)
106
140
  else()
@@ -141,9 +175,14 @@ if(LIB_ARCH)
141
175
  if(EMNAPI_BUILD_MT)
142
176
  install(TARGETS ${EMNAPI_MT_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
143
177
  endif()
178
+ if(EMNAPI_BUILD_BASIC_MT)
179
+ install(TARGETS ${EMNAPI_BASIC_MT_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
180
+ endif()
144
181
  if(IS_WASM32)
145
182
  install(TARGETS ${DLMALLOC_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
183
+ install(TARGETS ${DLMALLOC_MT_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
146
184
  install(TARGETS ${EMMALLOC_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
185
+ install(TARGETS ${EMMALLOC_MT_TARGET_NAME} DESTINATION "lib/${LIB_ARCH}")
147
186
  endif()
148
187
  endif()
149
188
 
@@ -178,4 +217,7 @@ if(EMNAPI_INSTALL_SRC)
178
217
  install(DIRECTORY
179
218
  ${CMAKE_CURRENT_SOURCE_DIR}/src/malloc
180
219
  DESTINATION "src/${PROJECT_NAME}")
220
+ install(DIRECTORY
221
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/thread
222
+ DESTINATION "src/${PROJECT_NAME}")
181
223
  endif()
package/README.md CHANGED
@@ -6,7 +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 with wasm support. [napi-rs support is comming soon](https://github.com/napi-rs/napi-rs/tree/emnapi).
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.
10
10
 
11
11
  This project aims to
12
12
 
@@ -25,16 +25,6 @@ See documentation for more details:
25
25
 
26
26
  [How to build Node-API official examples](https://github.com/toyobayashi/node-addon-examples)
27
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
-
38
28
  ## Prerequests
39
29
 
40
30
  You will need to install:
@@ -275,6 +265,9 @@ declare namespace Module {
275
265
  * napi_create_async_work and napi_create_threadsafe_function
276
266
  */
277
267
  nodeBinding?: typeof import('@emnapi/node-binding')
268
+
269
+ /** See Multithread part */
270
+ asyncWorkPoolSize?: number
278
271
  }
279
272
  export function emnapiInit (options: EmnapiInitOptions): any
280
273
  }
@@ -732,20 +725,60 @@ pub unsafe extern "C" fn napi_register_wasm_v1(env: napi_env, exports: napi_valu
732
725
 
733
726
  ### Multithread
734
727
 
735
- If you want to use async work or thread safe functions,
736
- there are additional C source file need to be compiled and linking.
737
- Recommend use CMake directly.
728
+ Related API:
729
+
730
+ - [napi_*_async_work](https://nodejs.org/dist/latest/docs/api/n-api.html#napi_create_async_work)
731
+ - [napi_*_threadsafe_function](https://nodejs.org/dist/latest/docs/api/n-api.html#asynchronous-thread-safe-function-calls)
732
+
733
+ They are available in emnapi, but you need to know more details before you start to use them.
734
+ Now emnapi has 3 implementations of async work and 2 implementations of TSFN:
735
+
736
+ - Async work
737
+ - A. Libuv threadpool and pthread based implementation in C
738
+ - B. Single thread mock in JavaScript
739
+ - C. Web worker based implementation in C (stack allocation) and JavaScript
740
+ - TSFN
741
+ - D. Libuv and pthread based implementation in C
742
+ - E. Web worker based implementation in JavaScript
743
+
744
+ | | Library to Link | `wasm32-emscripten` | `wasm32` | `wasm32-wasi` | `wasm32-wasi-threads` |
745
+ |---|------------------------|---------------------|----------|---------------|-----------------------|
746
+ | A | libemnapi-mt.a | ✅ | ❌ | ❌ | ✅ |
747
+ | B | libemnapi-basic(-mt).a | ✅ | ✅ | ✅ | ✅ |
748
+ | C | libemnapi-basic-mt.a | ❌ | ✅ | ❌ | ✅ |
749
+ | D | libemnapi-mt.a | ✅ | ❌ | ❌ | ✅ |
750
+ | E | libemnapi-basic-mt.a | ✅ | ✅ | ✅ | ✅ |
738
751
 
739
- **This is EXPERIMENTAL on non-emscripten.**
752
+ There are some limitations on browser about wasi-libc's pthread implementation, for example
753
+ `pthread_mutex_lock` may call `__builtin_wasm_memory_atomic_wait32`(`memory.atomic.wait32`)
754
+ which is disallowed in browser JS main thread. While Emscripten's pthread implementation
755
+ has considered usage in browser. If you need to run your addon with multithreaded features on browser,
756
+ we recommend you use Emscripten A & D, or bare wasm32 C & E.
757
+
758
+ #### About Prebuilt Libraries
759
+
760
+ Prebuilt libraries can be found in the `lib` directory in `emnapi` npm package.
761
+
762
+ | Library | Description | `wasm32-emscripten` | `wasm32` | `wasm32-wasi` | `wasm32-wasi-threads` |
763
+ |----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------|----------|---------------|-----------------------------------------|
764
+ | libemnapi.a | no atomics feature.<br/><br/> no libuv port.<br/><br/> `napi_*_async_work` and `napi_*_threadsafe_function` always return `napi_generic_failure`. | ✅ | ✅ | ✅ | waiting wasi-sdk release thread support |
765
+ | libemnapi-mt.a | atomics feature enabled.<br/><br/> `napi_*_async_work` and `napi_*_threadsafe_function` are based on pthread and libuv port. | ✅ | ❌ | ❌ | waiting wasi-sdk release thread support |
766
+ | libemnapi-basic.a | no atomics feature.<br/><br/> no libuv port.<br/><br/> `napi_*_async_work` and `napi_*_threadsafe_function` are imported from JavaScript land. | ✅ | ✅ | ✅ | waiting wasi-sdk release thread support |
767
+ | libemnapi-basic-mt.a | atomics feature enabled.<br/><br/> no libuv port.<br/><br/> `napi_*_async_work` and `napi_*_threadsafe_function` are imported from JavaScript land.<br/><br/> include `emnapi_async_worker_create` and `emnapi_async_worker_init` for WebWorker based async work implementation. | ❌ | ✅ | ✅ | waiting wasi-sdk release thread support |
768
+ | libdlmalloc.a | no atomics feature, no thread safe garanteed. | ❌ | ✅ | ❌ | ❌ |
769
+ | libdlmalloc-mt.a | atomics feature enabled, thread safe. | ❌ | ✅ | ❌ | ❌ |
770
+ | libemmalloc.a | no atomics feature, no thread safe garanteed. | ❌ | ✅ | ❌ | ❌ |
771
+ | libemmalloc-mt.a | atomics feature enabled, thread safe. | ❌ | ✅ | ❌ | ❌ |
772
+
773
+ #### Usage
740
774
 
741
775
  ```cmake
742
776
  add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/emnapi")
743
777
 
744
778
  add_executable(hello hello.c)
745
779
 
746
- target_link_libraries(hello emnapi-mt)
747
-
748
780
  if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
781
+ target_link_libraries(hello emnapi-mt)
749
782
  target_compile_options(hello PRIVATE "-pthread")
750
783
  target_link_options(hello PRIVATE
751
784
  "-sALLOW_MEMORY_GROWTH=1"
@@ -758,6 +791,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
758
791
  )
759
792
  elseif(CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads")
760
793
  # Experimental
794
+ target_link_libraries(hello emnapi-mt)
761
795
  set_target_properties(hello PROPERTIES SUFFIX ".wasm")
762
796
  target_compile_options(hello PRIVATE "-fno-exceptions" "-pthread")
763
797
  target_link_options(hello PRIVATE
@@ -771,6 +805,19 @@ elseif(CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads")
771
805
  "-Wl,--import-undefined"
772
806
  "-Wl,--export-table"
773
807
  )
808
+ elseif((CMAKE_C_COMPILER_TARGET STREQUAL "wasm32") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-unknown-unknown"))
809
+ target_link_libraries(hello emnapi-basic-mt)
810
+ set_target_properties(hello PROPERTIES SUFFIX ".wasm")
811
+ target_compile_options(hello PRIVATE "-fno-exceptions" "-matomics" "-mbulk-memory")
812
+ target_link_options(hello PRIVATE
813
+ "-nostdlib"
814
+ "-Wl,--no-entry"
815
+ "-Wl,--export=napi_register_wasm_v1"
816
+ "-Wl,--export=emnapi_async_worker_create"
817
+ "-Wl,--export=emnapi_async_worker_init"
818
+ "-Wl,--import-memory,--shared-memory,--max-memory=2147483648,--import-undefined"
819
+ "-Wl,--export-dynamic,--export=malloc,--export=free,--export-table"
820
+ )
774
821
  endif()
775
822
  ```
776
823
 
@@ -784,6 +831,11 @@ cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk-pthread.cmake \
784
831
  -DCMAKE_BUILD_TYPE=Release \
785
832
  -G Ninja -H. -Bbuild
786
833
 
834
+ cmake -DCMAKE_TOOLCHAIN_FILE=node_modules/emnapi/cmake/wasm32.cmake \
835
+ -DWASI_SDK_PREFIX=$WASI_SDK_PATH \
836
+ -DCMAKE_BUILD_TYPE=Release \
837
+ -G Ninja -H. -Bbuild
838
+
787
839
  cmake --build build
788
840
  ```
789
841
 
@@ -793,6 +845,15 @@ And additional work is required during instantiating wasm compiled with non-emsc
793
845
  // emnapi main thread (could be in a Worker)
794
846
  instantiateNapiModule(input, {
795
847
  context: getDefaultContext(),
848
+ /**
849
+ * emscripten
850
+ * 0: no effect
851
+ * > 0: the same effect to UV_THREADPOOL_SIZE
852
+ * non-emscripten
853
+ * 0: single thread mock
854
+ * > 0 schedule async work in web worker
855
+ */
856
+ asyncWorkPoolSize: 4, // 0: single thread mock, > 0: schedule async work in web worker
796
857
  wasi: new WASI(/* ... */),
797
858
  // reuseWorker: true,
798
859
  onCreateWorker () {
@@ -890,17 +951,30 @@ instantiateNapiModule(input, {
890
951
 
891
952
  ### `-DEMNAPI_WORKER_POOL_SIZE=4`
892
953
 
893
- 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:
954
+ 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 `asyncWorkPoolSize` option or `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:
894
955
 
895
956
  ```js
957
+ Module.init({
958
+ // ...
959
+ asyncWorkPoolSize: 2
960
+ })
961
+
962
+ // if asyncWorkPoolSize is not specified
896
963
  Module.preRun = Module.preRun || [];
897
964
  Module.preRun.push(function () {
898
965
  if (typeof ENV !== 'undefined') {
899
966
  ENV.UV_THREADPOOL_SIZE = '2';
900
967
  }
901
968
  });
969
+ ```
902
970
 
971
+ ```js
903
972
  // wasi
973
+ instantiateNapiModule({
974
+ // ...
975
+ asyncWorkPoolSize: 2
976
+ })
977
+ // if asyncWorkPoolSize is not specified
904
978
  new WASI({
905
979
  env: {
906
980
  UV_THREADPOOL_SIZE: '2'
@@ -908,7 +982,7 @@ new WASI({
908
982
  })
909
983
  ```
910
984
 
911
- 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.
985
+ It represent max of `EMNAPI_WORKER_POOL_SIZE` async work (`napi_queue_async_work`) can be executed in parallel. Default is not defined.
912
986
 
913
987
  You can set both `PTHREAD_POOL_SIZE` and `EMNAPI_WORKER_POOL_SIZE` to `number of CPU cores` in general.
914
988
  If you use another library function which may create `N` child threads in async work,
@@ -1,37 +1,33 @@
1
1
  {{{ ((DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.indexOf("$emnapiInit") === -1 ? DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push("$emnapiInit") : undefined), "") }}}
2
2
  {{{ ((EXPORTED_RUNTIME_METHODS.indexOf("emnapiInit") === -1 ? EXPORTED_RUNTIME_METHODS.push("emnapiInit") : undefined), "") }}}
3
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
4
- function emnapiCreateIdGenerator() {
5
- var obj = {
6
- nextId: 1,
7
- list: [],
8
- generate: function () {
9
- var id;
10
- if (obj.list.length) {
11
- id = obj.list.shift();
12
- }
13
- else {
14
- id = obj.nextId;
15
- obj.nextId++;
16
- }
17
- return id;
18
- },
19
- reuse: function (id) {
20
- obj.list.push(id);
21
- }
22
- };
23
- return obj;
24
- }
25
- var emnapiAsyncWork = {
3
+ var emnapiAWST = {
26
4
  idGen: {},
27
5
  values: [undefined],
28
6
  queued: new Set(),
29
7
  pending: [],
30
8
  init: function () {
31
- emnapiAsyncWork.idGen = emnapiCreateIdGenerator();
32
- emnapiAsyncWork.values = [undefined];
33
- emnapiAsyncWork.queued = new Set();
34
- emnapiAsyncWork.pending = [];
9
+ var idGen = {
10
+ nextId: 1,
11
+ list: [],
12
+ generate: function () {
13
+ var id;
14
+ if (idGen.list.length) {
15
+ id = idGen.list.shift();
16
+ }
17
+ else {
18
+ id = idGen.nextId;
19
+ idGen.nextId++;
20
+ }
21
+ return id;
22
+ },
23
+ reuse: function (id) {
24
+ idGen.list.push(id);
25
+ }
26
+ };
27
+ emnapiAWST.idGen = idGen;
28
+ emnapiAWST.values = [undefined];
29
+ emnapiAWST.queued = new Set();
30
+ emnapiAWST.pending = [];
35
31
  },
36
32
  create: function (env, resource, resourceName, execute, complete, data) {
37
33
  var asyncId = 0;
@@ -41,12 +37,11 @@ var emnapiAsyncWork = {
41
37
  asyncId = asyncContext.asyncId;
42
38
  triggerAsyncId = asyncContext.triggerAsyncId;
43
39
  }
44
- var id = emnapiAsyncWork.idGen.generate();
45
- emnapiAsyncWork.values[id] = {
40
+ var id = emnapiAWST.idGen.generate();
41
+ emnapiAWST.values[id] = {
46
42
  env: env,
47
43
  id: id,
48
44
  resource: resource,
49
- resourceName: resourceName,
50
45
  asyncId: asyncId,
51
46
  triggerAsyncId: triggerAsyncId,
52
47
  status: 0,
@@ -62,6 +57,8 @@ var emnapiAsyncWork = {
62
57
  var env = work.env;
63
58
  var data = work.data;
64
59
  var callback = function () {
60
+ if (!complete)
61
+ return;
65
62
  var envObject = emnapiCtx.envStore.get(env);
66
63
  var scope = emnapiCtx.openScope(envObject);
67
64
  try {
@@ -84,16 +81,16 @@ var emnapiAsyncWork = {
84
81
  }
85
82
  },
86
83
  queue: function (id) {
87
- var work = emnapiAsyncWork.values[id];
84
+ var work = emnapiAWST.values[id];
88
85
  if (!work)
89
86
  return;
90
87
  if (work.status === 0) {
91
88
  work.status = 1;
92
- if (emnapiAsyncWork.queued.size >= 4) {
93
- emnapiAsyncWork.pending.push(id);
89
+ if (emnapiAWST.queued.size >= (Math.abs(emnapiAsyncWorkPoolSize) || 4)) {
90
+ emnapiAWST.pending.push(id);
94
91
  return;
95
92
  }
96
- emnapiAsyncWork.queued.add(id);
93
+ emnapiAWST.queued.add(id);
97
94
  var env_1 = work.env;
98
95
  var data_1 = work.data;
99
96
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -101,25 +98,29 @@ var emnapiAsyncWork = {
101
98
  work.status = 2;
102
99
  emnapiCtx.feature.setImmediate(function () {
103
100
  {{{ makeDynCall('vpp', 'execute') }}}(env_1, data_1);
104
- emnapiAsyncWork.queued.delete(id);
101
+ emnapiAWST.queued.delete(id);
105
102
  work.status = 3;
106
- emnapiAsyncWork.callComplete(work, 0 /* napi_status.napi_ok */);
107
- if (emnapiAsyncWork.pending.length > 0) {
108
- var nextWorkId = emnapiAsyncWork.pending.shift();
109
- emnapiAsyncWork.values[nextWorkId].status = 0;
110
- emnapiAsyncWork.queue(nextWorkId);
103
+ emnapiCtx.feature.setImmediate(function () {
104
+ emnapiAWST.callComplete(work, 0 /* napi_status.napi_ok */);
105
+ });
106
+ if (emnapiAWST.pending.length > 0) {
107
+ var nextWorkId = emnapiAWST.pending.shift();
108
+ emnapiAWST.values[nextWorkId].status = 0;
109
+ emnapiAWST.queue(nextWorkId);
111
110
  }
112
111
  });
113
112
  }
114
113
  },
115
114
  cancel: function (id) {
116
- var index = emnapiAsyncWork.pending.indexOf(id);
115
+ var index = emnapiAWST.pending.indexOf(id);
117
116
  if (index !== -1) {
118
- var work = emnapiAsyncWork.values[id];
119
- if (work && (work.status === 1)) {
120
- work.status = 4;
121
- emnapiAsyncWork.pending.splice(index, 1);
122
- emnapiAsyncWork.callComplete(work, 11 /* napi_status.napi_cancelled */);
117
+ var work_1 = emnapiAWST.values[id];
118
+ if (work_1 && (work_1.status === 1)) {
119
+ work_1.status = 4;
120
+ emnapiAWST.pending.splice(index, 1);
121
+ emnapiCtx.feature.setImmediate(function () {
122
+ emnapiAWST.callComplete(work_1, 11 /* napi_status.napi_cancelled */);
123
+ });
123
124
  return 0 /* napi_status.napi_ok */;
124
125
  }
125
126
  else {
@@ -129,7 +130,7 @@ var emnapiAsyncWork = {
129
130
  return 9 /* napi_status.napi_generic_failure */;
130
131
  },
131
132
  remove: function (id) {
132
- var work = emnapiAsyncWork.values[id];
133
+ var work = emnapiAWST.values[id];
133
134
  if (!work)
134
135
  return;
135
136
  if (emnapiNodeBinding) {
@@ -138,68 +139,11 @@ var emnapiAsyncWork = {
138
139
  triggerAsyncId: work.triggerAsyncId
139
140
  });
140
141
  }
141
- emnapiAsyncWork.values[id] = undefined;
142
- emnapiAsyncWork.idGen.reuse(id);
142
+ emnapiAWST.values[id] = undefined;
143
+ emnapiAWST.idGen.reuse(id);
143
144
  }
144
145
  };
145
- function _napi_create_async_work(env, resource, resource_name, execute, complete, data, result) {
146
- if (env == 0)
147
- return 1 /* napi_status.napi_invalid_arg */;
148
- var envObject = emnapiCtx.envStore.get(env);
149
- if (execute == 0)
150
- return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
151
- if (result == 0)
152
- return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
153
- var resourceObject;
154
- if (resource) {
155
- resourceObject = Object(emnapiCtx.handleStore.get(resource).value);
156
- }
157
- else {
158
- resourceObject = {};
159
- }
160
- if (resource_name == 0)
161
- return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
162
- var resourceName = String(emnapiCtx.handleStore.get(resource_name).value);
163
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
164
- var id = emnapiAsyncWork.create(env, resourceObject, resourceName, execute, complete, data);
165
- {{{ makeSetValue('result', 0, 'id', '*') }}};
166
- return envObject.clearLastError();
167
- }
168
- function _napi_delete_async_work(env, work) {
169
- if (env == 0)
170
- return 1 /* napi_status.napi_invalid_arg */;
171
- var envObject = emnapiCtx.envStore.get(env);
172
- if (work == 0)
173
- return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
174
- emnapiAsyncWork.remove(work);
175
- return envObject.clearLastError();
176
- }
177
- function _napi_queue_async_work(env, work) {
178
- if (env == 0)
179
- return 1 /* napi_status.napi_invalid_arg */;
180
- var envObject = emnapiCtx.envStore.get(env);
181
- if (work == 0)
182
- return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
183
- emnapiAsyncWork.queue(work);
184
- return envObject.clearLastError();
185
- }
186
- function _napi_cancel_async_work(env, work) {
187
- if (env == 0)
188
- return 1 /* napi_status.napi_invalid_arg */;
189
- var envObject = emnapiCtx.envStore.get(env);
190
- if (work == 0)
191
- return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
192
- var status = emnapiAsyncWork.cancel(work);
193
- if (status === 0 /* napi_status.napi_ok */)
194
- return envObject.clearLastError();
195
- return envObject.setLastError(status);
196
- }
197
- emnapiImplementHelper('$emnapiCreateIdGenerator', undefined, emnapiCreateIdGenerator, []);
198
- emnapiDefineVar('$emnapiAsyncWork', emnapiAsyncWork, ['$emnapiCreateIdGenerator'], 'emnapiAsyncWork.init();');
199
- emnapiImplement('napi_create_async_work', 'ippppppp', _napi_create_async_work, ['$emnapiAsyncWork']);
200
- emnapiImplement('napi_delete_async_work', 'ipp', _napi_delete_async_work, ['$emnapiAsyncWork']);
201
- emnapiImplement('napi_queue_async_work', 'ipp', _napi_queue_async_work, ['$emnapiAsyncWork']);
202
- emnapiImplement('napi_cancel_async_work', 'ipp', _napi_cancel_async_work, ['$emnapiAsyncWork']);
146
+ emnapiDefineVar('$emnapiAWST', emnapiAWST, ['$emnapiAsyncWorkPoolSize'], 'emnapiAWST.init();');
203
147
  function _emnapi_create_memory_view(env, typedarray_type, external_data, byte_length, finalize_cb, finalize_hint, result
204
148
  // @ts-expect-error
205
149
  ) {
@@ -5468,6 +5412,62 @@ emnapiImplement('napi_get_boolean', 'ipip', napi_get_boolean);
5468
5412
  emnapiImplement('napi_get_global', 'ipp', napi_get_global);
5469
5413
  emnapiImplement('napi_get_null', 'ipp', napi_get_null);
5470
5414
  emnapiImplement('napi_get_undefined', 'ipp', napi_get_undefined);
5415
+ function _napi_create_async_work(env, resource, resource_name, execute, complete, data, result) {
5416
+ if (env == 0)
5417
+ return 1 /* napi_status.napi_invalid_arg */;
5418
+ var envObject = emnapiCtx.envStore.get(env);
5419
+ if (execute == 0)
5420
+ return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
5421
+ if (result == 0)
5422
+ return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
5423
+ var resourceObject;
5424
+ if (resource) {
5425
+ resourceObject = Object(emnapiCtx.handleStore.get(resource).value);
5426
+ }
5427
+ else {
5428
+ resourceObject = {};
5429
+ }
5430
+ if (resource_name == 0)
5431
+ return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
5432
+ var resourceName = String(emnapiCtx.handleStore.get(resource_name).value);
5433
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
5434
+ var id = emnapiAWST.create(env, resourceObject, resourceName, execute, complete, data);
5435
+ {{{ makeSetValue('result', 0, 'id', '*') }}};
5436
+ return envObject.clearLastError();
5437
+ }
5438
+ function _napi_delete_async_work(env, work) {
5439
+ if (env == 0)
5440
+ return 1 /* napi_status.napi_invalid_arg */;
5441
+ var envObject = emnapiCtx.envStore.get(env);
5442
+ if (work == 0)
5443
+ return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
5444
+ emnapiAWST.remove(work);
5445
+ return envObject.clearLastError();
5446
+ }
5447
+ function _napi_queue_async_work(env, work) {
5448
+ if (env == 0)
5449
+ return 1 /* napi_status.napi_invalid_arg */;
5450
+ var envObject = emnapiCtx.envStore.get(env);
5451
+ if (work == 0)
5452
+ return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
5453
+ emnapiAWST.queue(work);
5454
+ return envObject.clearLastError();
5455
+ }
5456
+ function _napi_cancel_async_work(env, work) {
5457
+ if (env == 0)
5458
+ return 1 /* napi_status.napi_invalid_arg */;
5459
+ var envObject = emnapiCtx.envStore.get(env);
5460
+ if (work == 0)
5461
+ return envObject.setLastError(1 /* napi_status.napi_invalid_arg */);
5462
+ var status = emnapiAWST.cancel(work);
5463
+ if (status === 0 /* napi_status.napi_ok */)
5464
+ return envObject.clearLastError();
5465
+ return envObject.setLastError(status);
5466
+ }
5467
+ emnapiImplement('napi_create_async_work', 'ippppppp', _napi_create_async_work, ['$emnapiAWST']);
5468
+ emnapiImplement('napi_delete_async_work', 'ipp', _napi_delete_async_work, ['$emnapiAWST']);
5469
+ emnapiImplement('napi_queue_async_work', 'ipp', _napi_queue_async_work, ['$emnapiAWST']);
5470
+ emnapiImplement('napi_cancel_async_work', 'ipp', _napi_cancel_async_work, ['$emnapiAWST']);
5471
5471
  mergeInto(LibraryManager.library, {
5472
5472
  _emnapi_worker_unref__sig: 'vp',
5473
5473
  _emnapi_worker_unref__deps: ['$PThread'],
@@ -5618,6 +5618,7 @@ emnapiDefineVar('$emnapiModule', {
5618
5618
  loaded: false,
5619
5619
  filename: ''
5620
5620
  });
5621
+ emnapiDefineVar('$emnapiAsyncWorkPoolSize', 0);
5621
5622
  function emnapiInit(options) {
5622
5623
  if (emnapiModule.loaded)
5623
5624
  return emnapiModule.exports;
@@ -5639,6 +5640,18 @@ function emnapiInit(options) {
5639
5640
  }
5640
5641
  emnapiNodeBinding = nodeBinding;
5641
5642
  }
5643
+ if ('asyncWorkPoolSize' in options) {
5644
+ if (typeof options.asyncWorkPoolSize !== 'number') {
5645
+ throw new TypeError('options.asyncWorkPoolSize must be a integer');
5646
+ }
5647
+ emnapiAsyncWorkPoolSize = options.asyncWorkPoolSize >> 0;
5648
+ if (emnapiAsyncWorkPoolSize > 1024) {
5649
+ emnapiAsyncWorkPoolSize = 1024;
5650
+ }
5651
+ else if (emnapiAsyncWorkPoolSize < -1024) {
5652
+ emnapiAsyncWorkPoolSize = -1024;
5653
+ }
5654
+ }
5642
5655
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
5643
5656
  var envObject = emnapiModule.envObject || (emnapiModule.envObject = emnapiCtx.createEnv(function (cb) { return {{{ makeDynCall('vppp', 'cb') }}}; }, function (cb) { return {{{ makeDynCall('vp', 'cb') }}}; }));
5644
5657
  var scope = emnapiCtx.openScope(envObject);
@@ -5660,7 +5673,11 @@ function emnapiInit(options) {
5660
5673
  delete emnapiModule.envObject;
5661
5674
  return emnapiModule.exports;
5662
5675
  }
5663
- emnapiImplementHelper('$emnapiInit', undefined, emnapiInit, ['$emnapiModule', '$emnapiCtx', '$emnapiNodeBinding', 'napi_register_wasm_v1']);
5676
+ emnapiImplementHelper('$emnapiInit', undefined, emnapiInit, ['$emnapiModule', '$emnapiCtx', '$emnapiNodeBinding', '$emnapiAsyncWorkPoolSize', 'napi_register_wasm_v1']);
5677
+ function __emnapi_async_work_pool_size() {
5678
+ return Math.abs(emnapiAsyncWorkPoolSize);
5679
+ }
5680
+ emnapiImplementInternal('_emnapi_async_work_pool_size', 'i', __emnapi_async_work_pool_size, ['$emnapiAsyncWorkPoolSize']);
5664
5681
  function __emnapi_get_filename(buf, len) {
5665
5682
  if (!buf) {
5666
5683
  return lengthBytesUTF8(emnapiModule.filename);
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emnapi",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "Node-API implementation for Emscripten",
5
5
  "main": "index.js",
6
6
  "devDependencies": {
@@ -0,0 +1,76 @@
1
+ #include <stddef.h>
2
+ #include <stdint.h>
3
+
4
+ void* calloc(size_t n, size_t size);
5
+
6
+ extern unsigned char __heap_base;
7
+ extern unsigned char __data_end;
8
+ extern unsigned char __global_base;
9
+ extern __attribute__((__weak__)) unsigned char __stack_high;
10
+ extern __attribute__((__weak__)) unsigned char __stack_low;
11
+
12
+ #define ROUND_UP(x, ALIGNMENT) (((x)+ALIGNMENT-1)&-ALIGNMENT)
13
+ #define STACK_ALIGN 16
14
+ #define DEFAULT_STACK_MAX (8<<20)
15
+
16
+ struct worker_args {
17
+ void* stack_base;
18
+ void* tls_base;
19
+ };
20
+
21
+ extern void __wasm_init_tls(void*);
22
+
23
+ void* __copy_tls(unsigned char *mem) {
24
+ size_t tls_align = __builtin_wasm_tls_align();
25
+ volatile void* tls_base = __builtin_wasm_tls_base();
26
+ mem += tls_align;
27
+ mem -= (uintptr_t)mem & (tls_align-1);
28
+ __wasm_init_tls(mem);
29
+ __asm__("local.get %0\n"
30
+ "global.set __tls_base\n"
31
+ :: "r"(tls_base));
32
+ return mem;
33
+ }
34
+
35
+ __attribute__((visibility("default")))
36
+ void* emnapi_async_worker_create() {
37
+ size_t args_size = sizeof(struct worker_args);
38
+ size_t size = args_size;
39
+
40
+ size_t tls_size = __builtin_wasm_tls_size();
41
+
42
+ size_t tls_block_size = 0;
43
+ if (tls_size) {
44
+ size_t tls_align = __builtin_wasm_tls_align();
45
+ tls_block_size = ROUND_UP(tls_size + tls_align - 1, tls_align);
46
+ size += tls_block_size;
47
+ }
48
+
49
+ ptrdiff_t stack_size = 0;
50
+ if (&__stack_high) {
51
+ stack_size = &__stack_high - &__stack_low;
52
+ } else {
53
+ unsigned char *sp;
54
+ __asm__(
55
+ ".globaltype __stack_pointer, i32\n"
56
+ "global.get __stack_pointer\n"
57
+ "local.set %0\n"
58
+ : "=r"(sp));
59
+ stack_size = sp > &__global_base ? &__heap_base - &__data_end : (ptrdiff_t)&__global_base;
60
+ }
61
+
62
+ stack_size = ROUND_UP(stack_size, STACK_ALIGN);
63
+ stack_size = stack_size < DEFAULT_STACK_MAX ? stack_size : DEFAULT_STACK_MAX;
64
+ size += stack_size;
65
+
66
+ void* block = calloc(1, size);
67
+ if (!block) return 0;
68
+
69
+ void* tls_base = tls_size ? __copy_tls(block + args_size) : 0;
70
+
71
+ struct worker_args* args = (struct worker_args*)block;
72
+
73
+ args->stack_base = block + args_size + tls_block_size + stack_size;
74
+ args->tls_base = tls_base;
75
+ return block;
76
+ }
@@ -0,0 +1,33 @@
1
+ #ifdef __wasm64__
2
+ #define PTR i64
3
+ #else
4
+ #define PTR i32
5
+ #endif
6
+
7
+ .text
8
+
9
+ .export_name emnapi_async_worker_init, emnapi_async_worker_init
10
+
11
+ .globaltype __stack_pointer, PTR
12
+ .globaltype __tls_base, PTR
13
+
14
+ .hidden emnapi_async_worker_init
15
+ .globl emnapi_async_worker_init
16
+ .type emnapi_async_worker_init,@function
17
+
18
+ emnapi_async_worker_init:
19
+ .functype emnapi_async_worker_init (PTR) -> ()
20
+
21
+ local.get 0
22
+ PTR.load 0
23
+ global.set __stack_pointer
24
+
25
+ local.get 0
26
+ #ifdef __wasm64__
27
+ PTR.load 8
28
+ #else
29
+ PTR.load 4
30
+ #endif
31
+ global.set __tls_base
32
+
33
+ end_function
@@ -209,6 +209,7 @@ static void post(QUEUE* q, enum uv__work_kind kind) {
209
209
  // nthreads = 0;
210
210
  // }
211
211
 
212
+ EMNAPI_EXTERN int _emnapi_async_work_pool_size();
212
213
 
213
214
  static void init_threads(void) {
214
215
  unsigned int i;
@@ -223,9 +224,14 @@ static void init_threads(void) {
223
224
  nthreads = EMNAPI_WORKER_POOL_SIZE;
224
225
  #else
225
226
  nthreads = ARRAY_SIZE(default_threads);
226
- val = getenv("UV_THREADPOOL_SIZE");
227
- if (val != NULL)
228
- nthreads = atoi(val);
227
+ int async_work_pool_size = _emnapi_async_work_pool_size();
228
+ if (async_work_pool_size > 0) {
229
+ nthreads = (unsigned int) async_work_pool_size;
230
+ } else {
231
+ val = getenv("UV_THREADPOOL_SIZE");
232
+ if (val != NULL)
233
+ nthreads = atoi(val);
234
+ }
229
235
  #endif
230
236
  if (nthreads == 0)
231
237
  nthreads = 1;