Gathering detailed insights and metrics for @emnapi/wasi-threads
Gathering detailed insights and metrics for @emnapi/wasi-threads
Gathering detailed insights and metrics for @emnapi/wasi-threads
Gathering detailed insights and metrics for @emnapi/wasi-threads
Node-API implementation for Emscripten, wasi-sdk, clang wasm32 and napi-rs
npm install @emnapi/wasi-threads
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
149 Stars
987 Commits
6 Forks
2 Watching
8 Branches
4 Contributors
Updated on 28 Nov 2024
C (44.19%)
TypeScript (26.02%)
JavaScript (18.95%)
C++ (7.07%)
CMake (1.59%)
Python (1.15%)
HTML (0.91%)
Rust (0.09%)
Assembly (0.04%)
Cumulative downloads
Total Downloads
Last day
1.2%
343,901
Compared to previous day
Last week
8.5%
1,795,811
Compared to previous week
Last month
21.3%
7,101,640
Compared to previous month
Last year
0%
24,071,071
Compared to previous year
1
Node-API implementation for Emscripten, wasi-sdk and clang with wasm support.
This project aims to
This project also powers the WebAssembly feature for napi-rs, and enables many Node.js native addons to run on StackBlitz's WebContainer.
Node-API changes will be synchronized into this repo.
See documentation for more details:
中文文档:
How to build Node-API official examples
You will need to install:
>= v16.15.0
>= v8
>= v3.1.9
/ wasi-sdk / LLVM clang with wasm support>= v3.13
>= v10.2.0
>= 6.1.0
There are several choices to get make
for Windows user
mingw32-make
%Path%
then rename it to mingw32-make
nmake
in Visual Studio Developer Command Prompt
nmake
in Visual Studio Developer Command Prompt
Verify your environment:
1node -v 2npm -v 3emcc -v 4 5# clang -v 6# clang -print-targets # ensure wasm32 target exists 7 8cmake --version 9 10# if you use node-gyp 11node-gyp --version 12 13# if you use ninja 14ninja --version 15 16# if you use make 17make -v 18 19# if you use nmake in Visual Studio Developer Command Prompt 20nmake /?
You need to set EMSDK
and WASI_SDK_PATH
environment variables.
1git clone https://github.com/toyobayashi/emnapi.git 2cd ./emnapi 3npm install -g node-gyp 4npm install 5npm run build # output ./packages/*/dist 6node ./script/release.js # output ./out 7 8# test 9npm run rebuild:test 10npm test
See CONTRIBUTING for more details.
1npm install -D emnapi 2npm install @emnapi/runtime 3 4# for non-emscripten 5npm install @emnapi/core 6 7# if you use node-addon-api 8npm install node-addon-api
Each package should match the same version.
Create hello.c
.
1#include <node_api.h> 2 3#define NODE_API_CALL(env, the_call) \ 4 do { \ 5 if ((the_call) != napi_ok) { \ 6 const napi_extended_error_info *error_info; \ 7 napi_get_last_error_info((env), &error_info); \ 8 bool is_pending; \ 9 const char* err_message = error_info->error_message; \ 10 napi_is_exception_pending((env), &is_pending); \ 11 if (!is_pending) { \ 12 const char* error_message = err_message != NULL ? \ 13 err_message : \ 14 "empty error message"; \ 15 napi_throw_error((env), NULL, error_message); \ 16 } \ 17 return NULL; \ 18 } \ 19 } while (0) 20 21static napi_value js_hello(napi_env env, napi_callback_info info) { 22 napi_value world; 23 const char* str = "world"; 24 NODE_API_CALL(env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &world)); 25 return world; 26} 27 28NAPI_MODULE_INIT() { 29 napi_value hello; 30 NODE_API_CALL(env, napi_create_function(env, "hello", NAPI_AUTO_LENGTH, 31 js_hello, NULL, &hello)); 32 NODE_API_CALL(env, napi_set_named_property(env, exports, "hello", hello)); 33 return exports; 34}
The C code is equivalant to the following JavaScript:
1module.exports = (function (exports) { 2 const hello = function hello () { 3 // native code in js_hello 4 const world = 'world' 5 return world 6 } 7 8 exports.hello = hello 9 return exports 10})(module.exports)
1emcc -O3 \ 2 -DBUILDING_NODE_EXTENSION \ 3 "-DNAPI_EXTERN=__attribute__((__import_module__(\"env\")))" \ 4 -I./node_modules/emnapi/include/node \ 5 -L./node_modules/emnapi/lib/wasm32-emscripten \ 6 --js-library=./node_modules/emnapi/dist/library_napi.js \ 7 -sEXPORTED_FUNCTIONS="['_malloc','_free','_napi_register_wasm_v1','_node_api_module_get_api_version_v1']" \ 8 -o hello.js \ 9 hello.c \ 10 -lemnapi
1clang -O3 \ 2 -DBUILDING_NODE_EXTENSION \ 3 -I./node_modules/emnapi/include/node \ 4 -L./node_modules/emnapi/lib/wasm32-wasi \ 5 --target=wasm32-wasi \ 6 --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \ 7 -mexec-model=reactor \ 8 -Wl,--initial-memory=16777216 \ 9 -Wl,--export-dynamic \ 10 -Wl,--export=malloc \ 11 -Wl,--export=free \ 12 -Wl,--export=napi_register_wasm_v1 \ 13 -Wl,--export-if-defined=node_api_module_get_api_version_v1 \ 14 -Wl,--import-undefined \ 15 -Wl,--export-table \ 16 -o hello.wasm \ 17 hello.c \ 18 -lemnapi
Choose libdlmalloc.a
or libemmalloc.a
for malloc
and free
.
1clang -O3 \ 2 -DBUILDING_NODE_EXTENSION \ 3 -I./node_modules/emnapi/include/node \ 4 -L./node_modules/emnapi/lib/wasm32 \ 5 --target=wasm32 \ 6 -nostdlib \ 7 -Wl,--no-entry \ 8 -Wl,--initial-memory=16777216 \ 9 -Wl,--export-dynamic \ 10 -Wl,--export=malloc \ 11 -Wl,--export=free \ 12 -Wl,--export=napi_register_wasm_v1 \ 13 -Wl,--export-if-defined=node_api_module_get_api_version_v1 \ 14 -Wl,--import-undefined \ 15 -Wl,--export-table \ 16 -o hello.wasm \ 17 hello.c \ 18 -lemnapi \ 19 -ldlmalloc # -lemmalloc
To initialize emnapi, you need to import the emnapi runtime to create a Context
by createContext
or getDefaultContext
first.
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.
1declare namespace emnapi { 2 // module '@emnapi/runtime' 3 export class Context { /* ... */ } 4 /** Create a new context */ 5 export function createContext (): Context 6 /** Create or get */ 7 export function getDefaultContext (): Context 8 // ... 9}
then call Module.emnapiInit
after emscripten runtime initialized.
Module.emnapiInit
only do initialization once, it will always return the same binding exports after successfully initialized.
1declare namespace Module { 2 interface EmnapiInitOptions { 3 context: emnapi.Context 4 5 /** node_api_get_module_file_name */ 6 filename?: string 7 8 /** 9 * Support following async_hooks related things 10 * on Node.js runtime only 11 * 12 * napi_async_init, 13 * napi_async_destroy, 14 * napi_make_callback, 15 * async resource parameter of 16 * napi_create_async_work and napi_create_threadsafe_function 17 */ 18 nodeBinding?: typeof import('@emnapi/node-binding') 19 20 /** See Multithread part */ 21 asyncWorkPoolSize?: number 22 } 23 export function emnapiInit (options: EmnapiInitOptions): any 24}
1<script src="node_modules/@emnapi/runtime/dist/emnapi.min.js"></script> 2<script src="hello.js"></script> 3<script> 4Module.onRuntimeInitialized = function () { 5 var binding; 6 try { 7 binding = Module.emnapiInit({ context: emnapi.getDefaultContext() }); 8 } catch (err) { 9 console.error(err); 10 return; 11 } 12 var msg = 'hello ' + binding.hello(); 13 window.alert(msg); 14}; 15 16// if -sMODULARIZE=1 17Module({ /* Emscripten module init options */ }).then(function (Module) { 18 var binding = Module.emnapiInit({ context: emnapi.getDefaultContext() }); 19}); 20</script>
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!
Running on Node.js:
1const emnapi = require('@emnapi/runtime') 2const Module = require('./hello.js') 3 4Module.onRuntimeInitialized = function () { 5 let binding 6 try { 7 binding = Module.emnapiInit({ context: emnapi.getDefaultContext() }) 8 } catch (err) { 9 console.error(err) 10 return 11 } 12 const msg = `hello ${binding.hello()}` 13 console.log(msg) 14} 15 16// if -sMODULARIZE=1 17Module({ /* Emscripten module init options */ }).then((Module) => { 18 const binding = Module.emnapiInit({ context: emnapi.getDefaultContext() }) 19})
For non-emscripten, you need to use @emnapi/core
. The initialization is similar to emscripten.
1<script src="node_modules/@emnapi/runtime/dist/emnapi.min.js"></script> 2<script src="node_modules/@emnapi/core/dist/emnapi-core.min.js"></script> 3<script> 4emnapiCore.instantiateNapiModule(fetch('./hello.wasm'), { 5 context: emnapi.getDefaultContext(), 6 overwriteImports (importObject) { 7 // importObject.env = { 8 // ...importObject.env, 9 // ...importObject.napi, 10 // ...importObject.emnapi, 11 // // ... 12 // } 13 } 14}).then(({ instance, module, napiModule }) => { 15 const binding = napiModule.exports 16 // ... 17}) 18</script>
Using WASI on Node.js
1const { instantiateNapiModule } = require('@emnapi/core') 2const { getDefaultContext } = require('@emnapi/runtime') 3const { WASI } = require('wasi') 4const fs = require('fs') 5 6instantiateNapiModule(fs.promises.readFile('./hello.wasm'), { 7 wasi: new WASI({ /* ... */ }), 8 context: getDefaultContext(), 9 overwriteImports (importObject) { 10 // importObject.env = { 11 // ...importObject.env, 12 // ...importObject.napi, 13 // ...importObject.emnapi, 14 // // ... 15 // } 16 } 17}).then(({ instance, module, napiModule }) => { 18 const binding = napiModule.exports 19 // ... 20})
Using WASI on browser, you can use WASI polyfill in wasm-util, and memfs-browser
1import { instantiateNapiModule } from '@emnapi/core' 2import { getDefaultContext } from '@emnapi/runtime' 3import { WASI } from '@tybys/wasm-util' 4import { Volume, createFsFromVolume } from 'memfs-browser' 5 6const fs = createFsFromVolume(Volume.fromJSON({ /* ... */ })) 7instantiateNapiModule(fetch('./hello.wasm'), { 8 wasi: new WASI({ fs, /* ... */ }) 9 context: getDefaultContext(), 10 overwriteImports (importObject) { 11 // importObject.env = { 12 // ...importObject.env, 13 // ...importObject.napi, 14 // ...importObject.emnapi, 15 // // ... 16 // } 17 } 18}).then(({ instance, module, napiModule }) => { 19 const binding = napiModule.exports 20 // ... 21})
Require node-addon-api
>= 6.1.0
1npm install node-addon-api
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+)!
Create hello.cpp
.
1#include <napi.h> 2 3Napi::String Method(const Napi::CallbackInfo& info) { 4 Napi::Env env = info.Env(); 5 return Napi::String::New(env, "world"); 6} 7 8Napi::Object Init(Napi::Env env, Napi::Object exports) { 9 exports.Set(Napi::String::New(env, "hello"), 10 Napi::Function::New(env, Method)).Check(); 11 return exports; 12} 13 14NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
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.
1em++ -O3 \ 2 -DBUILDING_NODE_EXTENSION \ 3 "-DNAPI_EXTERN=__attribute__((__import_module__(\"env\")))" \ 4 -DNAPI_DISABLE_CPP_EXCEPTIONS \ 5 -DNODE_ADDON_API_ENABLE_MAYBE \ 6 -I./node_modules/emnapi/include/node \ 7 -I./node_modules/node-addon-api \ 8 -L./node_modules/emnapi/lib/wasm32-emscripten \ 9 --js-library=./node_modules/emnapi/dist/library_napi.js \ 10 -sEXPORTED_FUNCTIONS="['_malloc','_free','_napi_register_wasm_v1','_node_api_module_get_api_version_v1']" \ 11 -o hello.js \ 12 hello.cpp \ 13 -lemnapi
1clang++ -O3 \ 2 -DBUILDING_NODE_EXTENSION \ 3 -DNAPI_DISABLE_CPP_EXCEPTIONS \ 4 -DNODE_ADDON_API_ENABLE_MAYBE \ 5 -I./node_modules/emnapi/include/node \ 6 -I./node_modules/node-addon-api \ 7 -L./node_modules/emnapi/lib/wasm32-wasi \ 8 --target=wasm32-wasi \ 9 --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \ 10 -fno-exceptions \ 11 -mexec-model=reactor \ 12 -Wl,--initial-memory=16777216 \ 13 -Wl,--export-dynamic \ 14 -Wl,--export=malloc \ 15 -Wl,--export=free \ 16 -Wl,--export=napi_register_wasm_v1 \ 17 -Wl,--export-if-defined=node_api_module_get_api_version_v1 \ 18 -Wl,--import-undefined \ 19 -Wl,--export-table \ 20 -o hello.wasm \ 21 hello.cpp \ 22 -lemnapi
node-addon-api
is using the C++ standard libraries, so you must use WASI if you are using node-addon-api
.
You can still use wasm32-unknown-unknown
target if you use Node-API C API only in C++.
1clang++ -O3 \ 2 -DBUILDING_NODE_EXTENSION \ 3 -I./node_modules/emnapi/include/node \ 4 -L./node_modules/emnapi/lib/wasm32 \ 5 --target=wasm32 \ 6 -fno-exceptions \ 7 -nostdlib \ 8 -Wl,--no-entry \ 9 -Wl,--initial-memory=16777216 \ 10 -Wl,--export-dynamic \ 11 -Wl,--export=malloc \ 12 -Wl,--export=free \ 13 -Wl,--export=napi_register_wasm_v1 \ 14 -Wl,--export-if-defined=node_api_module_get_api_version_v1 \ 15 -Wl,--import-undefined \ 16 -Wl,--export-table \ 17 -o node_api_c_api_only.wasm \ 18 node_api_c_api_only.cpp \ 19 -lemnapi \ 20 -ldlmalloc # -lemmalloc
operator new
and operator delete
.
1#include <stddef.h> 2 3extern "C" void* malloc(size_t size); 4extern "C" void free(void* p); 5 6void* operator new(size_t size) { 7 return malloc(size); 8} 9 10void operator delete(void* p) noexcept { 11 free(p); 12}
Create CMakeLists.txt
.
1cmake_minimum_required(VERSION 3.13) 2 3project(emnapiexample) 4 5add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/emnapi") 6 7add_executable(hello hello.c) 8 9target_link_libraries(hello emnapi) 10if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") 11 target_link_options(hello PRIVATE 12 "-sEXPORTED_FUNCTIONS=['_malloc','_free','_napi_register_wasm_v1','_node_api_module_get_api_version_v1']" 13 ) 14elseif(CMAKE_SYSTEM_NAME STREQUAL "WASI") 15 set_target_properties(hello PROPERTIES SUFFIX ".wasm") 16 target_link_options(hello PRIVATE 17 "-mexec-model=reactor" 18 "-Wl,--export=napi_register_wasm_v1" 19 "-Wl,--export-if-defined=node_api_module_get_api_version_v1" 20 "-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table" 21 ) 22elseif((CMAKE_C_COMPILER_TARGET STREQUAL "wasm32") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-unknown-unknown")) 23 set_target_properties(hello PROPERTIES SUFFIX ".wasm") 24 target_link_options(hello PRIVATE 25 "-nostdlib" 26 "-Wl,--export=napi_register_wasm_v1" 27 "-Wl,--export-if-defined=node_api_module_get_api_version_v1" 28 "-Wl,--no-entry" 29 "-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table" 30 ) 31 target_link_libraries(hello dlmalloc) 32 # target_link_libraries(hello emmalloc) 33endif()
If you use node-addon-api, you can use -DEMNAPI_FIND_NODE_ADDON_API=ON
or manually add node-addon-api directory to the include dir via include_directories()
or target_include_directories()
.
1mkdir build 2 3# emscripten 4emcmake cmake -DCMAKE_BUILD_TYPE=Release \ 5 -DEMNAPI_FIND_NODE_ADDON_API=ON \ 6 -G Ninja -H. -Bbuild 7 8# wasi-sdk 9cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake \ 10 -DWASI_SDK_PREFIX=$WASI_SDK_PATH \ 11 -DEMNAPI_FIND_NODE_ADDON_API=ON \ 12 -DCMAKE_BUILD_TYPE=Release \ 13 -G Ninja -H. -Bbuild 14 15# wasm32 16cmake -DCMAKE_TOOLCHAIN_FILE=node_modules/emnapi/cmake/wasm32.cmake \ 17 -DLLVM_PREFIX=$WASI_SDK_PATH \ 18 -DCMAKE_BUILD_TYPE=Release \ 19 -G Ninja -H. -Bbuild 20 21cmake --build build
Output code can run in recent version modern browsers and Node.js latest LTS. IE is not supported.
Require node-gyp >= 10.2.0
See emnapi-node-gyp-test for examples.
Arch: node-gyp configure --arch=<wasm32 | wasm64>
1// node-gyp configure -- -Dvariable_name=value 2 3declare var OS: 'emscripten' | 'wasi' | 'unknown' | 'wasm' | '' 4 5/** 6 * Enable async work and threadsafe-functions 7 * @default 0 8 */ 9declare var wasm_threads: 0 | 1 10 11/** @default 1048576 */ 12declare var stack_size: number 13 14/** @default 16777216 */ 15declare var initial_memory: number 16 17/** @default 2147483648 */ 18declare var max_memory: number 19 20/** @default path.join(path.dirname(commonGypiPath,'./dist/library_napi.js')) */ 21declare var emnapi_js_library: string 22 23/** @default 0 */ 24declare var emnapi_manual_linking: 0 | 1
binding.gyp
1{ 2 "targets": [ 3 { 4 "target_name": "hello", 5 "sources": [ 6 "hello.c" 7 ], 8 "conditions": [ 9 ["OS == 'emscripten'", { 10 "product_extension": "js", # required 11 12 "cflags": [], 13 "cflags_c": [], 14 "cflags_cc": [], 15 "ldflags": [] 16 }], 17 ["OS == 'wasi'", { 18 # ... 19 }], 20 ["OS in ' wasm unknown'", { 21 # ... 22 }] 23 ] 24 } 25 ] 26}
1# Linux or macOS 2export GYP_CROSSCOMPILE=1 3 4# emscripten 5export AR_target="$EMSDK/upstream/emscripten/emar" 6export CC_target="$EMSDK/upstream/emscripten/emcc" 7export CXX_target="$EMSDK/upstream/emscripten/em++" 8 9# wasi-sdk 10export AR_target="$WASI_SDK_PATH/bin/ar" 11export CC_target="$WASI_SDK_PATH/bin/clang" 12export CXX_target="$WASI_SDK_PATH/bin/clang++"
1@REM Windows 2 3set GYP_CROSSCOMPILE=1 4 5@REM emscripten 6call set AR_target=%%EMSDK:\=/%%/upstream/emscripten/emar.bat 7call set CC_target=%%EMSDK:\=/%%/upstream/emscripten/emcc.bat 8call set CXX_target=%%EMSDK:\=/%%/upstream/emscripten/em++.bat 9 10@REM wasi-sdk 11call set AR_target=%%WASI_SDK_PATH:\=/%%/bin/ar.exe 12call set CC_target=%%WASI_SDK_PATH:\=/%%/bin/clang.exe 13call set CXX_target=%%WASI_SDK_PATH:\=/%%/bin/clang++.exe
1# Linux or macOS 2 3# emscripten 4emmake node-gyp rebuild \ 5 --arch=wasm32 \ 6 --nodedir=./node_modules/emnapi \ 7 -- -f make-emscripten # -Dwasm_threads=1 8 9# wasi 10node-gyp rebuild \ 11 --arch=wasm32 \ 12 --nodedir=./node_modules/emnapi \ 13 -- -f make-wasi # -Dwasm_threads=1 14 15# bare wasm32 16node-gyp rebuild \ 17 --arch=wasm32 \ 18 --nodedir=./node_modules/emnapi \ 19 -- -f make-wasm # -Dwasm_threads=1
1@REM Use make generator on Windows 2@REM Run the bat file in POSIX-like environment (e.g. Cygwin) 3 4@REM emscripten 5call npx.cmd node-gyp configure --arch=wasm32 --nodedir=./node_modules/emnapi -- -f make-emscripten 6call emmake.bat make -C %~dp0build 7 8@REM wasi 9call npx.cmd node-gyp configure --arch=wasm32 --nodedir=./node_modules/emnapi -- -f make-wasi 10make -C %~dp0build 11 12@REM bare wasm32 13call npx.cmd node-gyp configure --arch=wasm32 --nodedir=./node_modules/emnapi -- -f make-wasm 14make -C %~dp0build
See napi-rs
Related API:
They are available in emnapi, but you need to know more details before you start to use them. Now emnapi has 3 implementations of async work and 2 implementations of TSFN:
Library to Link | wasm32-emscripten | wasm32 | wasm32-wasi | wasm32-wasi-threads | |
---|---|---|---|---|---|
A | libemnapi-mt.a | ✅ | ❌ | ❌ | ✅ |
B | libemnapi-basic(-mt).a | ✅ | ✅ | ✅ | ✅ |
C | libemnapi-basic-mt.a | ❌ | ✅ | ❌ | ✅ |
D | libemnapi-mt.a | ✅ | ❌ | ❌ | ✅ |
E | libemnapi-basic(-mt).a | ✅ | ✅ | ✅ | ✅ |
There are some limitations on browser about wasi-libc's pthread implementation, for example
pthread_mutex_lock
may call __builtin_wasm_memory_atomic_wait32
(memory.atomic.wait32
)
which is disallowed in browser JS main thread. While Emscripten's pthread implementation
has considered usage in browser. If you need to run your addon with multithreaded features on browser,
we recommend you use Emscripten A & D, or bare wasm32 C & E.
Note: For browsers, all the multithreaded features relying on Web Workers (Emscripten pthread also relying on Web Workers)
require cross-origin isolation to enable SharedArrayBuffer
. You can make a page cross-origin isolated
by serving the page with these headers:
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
If you would like to avoid SharedArrayBuffer
and cross-origin isolation, please use B & E (link against libemnapi-basic.a
), see the following table for more details.
Prebuilt libraries can be found in the lib
directory in emnapi
npm package.
Library | Description | wasm32-emscripten | wasm32 | wasm32-wasi | wasm32-wasi-threads |
---|---|---|---|---|---|
libemnapi.a | no atomics feature. no libuv port. napi_*_async_work and napi_*_threadsafe_function always return napi_generic_failure . | ✅ | ✅ | ✅ | ✅ |
libemnapi-mt.a | atomics feature enabled.napi_*_async_work and napi_*_threadsafe_function are based on pthread and libuv port. | ✅ | ❌ | ❌ | ✅ |
libemnapi-basic.a | no atomics feature. no libuv port. napi_*_async_work and napi_*_threadsafe_function are imported from JavaScript land. | ✅ | ✅ | ✅ | ✅ |
libemnapi-basic-mt.a | atomics feature enabled. no libuv port. napi_*_async_work and napi_*_threadsafe_function are imported from JavaScript land.include emnapi_async_worker_create and emnapi_async_worker_init for WebWorker based async work implementation. | ❌ | ✅ | ✅ | ✅ |
libdlmalloc.a | no atomics feature, no thread safe garanteed. | ❌ | ✅ | ❌ | ❌ |
libdlmalloc-mt.a | atomics feature enabled, thread safe. | ❌ | ✅ | ❌ | ❌ |
libemmalloc.a | no atomics feature, no thread safe garanteed. | ❌ | ✅ | ❌ | ❌ |
libemmalloc-mt.a | atomics feature enabled, thread safe. | ❌ | ✅ | ❌ | ❌ |
1add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/emnapi") 2 3add_executable(hello hello.c) 4 5if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") 6 target_link_libraries(hello emnapi-mt) 7 target_compile_options(hello PRIVATE "-pthread") 8 target_link_options(hello PRIVATE 9 "-sALLOW_MEMORY_GROWTH=1" 10 "-sEXPORTED_FUNCTIONS=['_malloc','_free','_napi_register_wasm_v1','_node_api_module_get_api_version_v1']" 11 "-pthread" 12 "-sPTHREAD_POOL_SIZE=4" 13 # try to specify stack size if you experience pthread errors 14 "-sSTACK_SIZE=2MB" 15 "-sDEFAULT_PTHREAD_STACK_SIZE=2MB" 16 ) 17elseif(CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads") 18 target_link_libraries(hello emnapi-mt) 19 set_target_properties(hello PROPERTIES SUFFIX ".wasm") 20 target_compile_options(hello PRIVATE "-fno-exceptions" "-pthread") 21 target_link_options(hello PRIVATE 22 "-pthread" 23 "-mexec-model=reactor" 24 "-Wl,--import-memory" 25 "-Wl,--max-memory=2147483648" 26 "-Wl,--export-dynamic" 27 "-Wl,--export=napi_register_wasm_v1" 28 "-Wl,--export-if-defined=node_api_module_get_api_version_v1" 29 "-Wl,--export=malloc" 30 "-Wl,--export=free" 31 "-Wl,--import-undefined" 32 "-Wl,--export-table" 33 ) 34elseif((CMAKE_C_COMPILER_TARGET STREQUAL "wasm32") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-unknown-unknown")) 35 target_link_libraries(hello emnapi-basic-mt) 36 set_target_properties(hello PROPERTIES SUFFIX ".wasm") 37 target_compile_options(hello PRIVATE "-fno-exceptions" "-matomics" "-mbulk-memory") 38 target_link_options(hello PRIVATE 39 "-nostdlib" 40 "-Wl,--no-entry" 41 "-Wl,--export=napi_register_wasm_v1" 42 "-Wl,--export-if-defined=node_api_module_get_api_version_v1" 43 "-Wl,--export=emnapi_async_worker_create" 44 "-Wl,--export=emnapi_async_worker_init" 45 "-Wl,--import-memory,--shared-memory,--max-memory=2147483648,--import-undefined" 46 "-Wl,--export-dynamic,--export=malloc,--export=free,--export-table" 47 ) 48endif()
1# emscripten 2emcmake cmake -DCMAKE_BUILD_TYPE=Release \ 3 -DEMNAPI_FIND_NODE_ADDON_API=ON \ 4 -DEMNAPI_WORKER_POOL_SIZE=4 \ 5 -G Ninja -H. -Bbuild 6 7# wasi-sdk with thread support 8cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk-pthread.cmake \ 9 -DWASI_SDK_PREFIX=$WASI_SDK_PATH \ 10 -DEMNAPI_FIND_NODE_ADDON_API=ON \ 11 -DCMAKE_BUILD_TYPE=Release \ 12 -G Ninja -H. -Bbuild 13 14cmake -DCMAKE_TOOLCHAIN_FILE=node_modules/emnapi/cmake/wasm32.cmake \ 15 -DLLVM_PREFIX=$WASI_SDK_PATH \ 16 -DCMAKE_BUILD_TYPE=Release \ 17 -G Ninja -H. -Bbuild 18 19cmake --build build
And additional work is required during instantiating wasm compiled with non-emscripten.
1// emnapi main thread (could be in a Worker) 2instantiateNapiModule(input, { 3 context: getDefaultContext(), 4 /** 5 * emscripten 6 * 0: no effect 7 * > 0: the same effect to UV_THREADPOOL_SIZE 8 * non-emscripten 9 * 0: single thread mock 10 * > 0 schedule async work in web worker 11 */ 12 asyncWorkPoolSize: 4, // 0: single thread mock, > 0: schedule async work in web worker 13 wasi: new WASI(/* ... */), 14 15 /** 16 * Setting this to `true` or a delay (ms) makes 17 * pthread_create() do not return until worker actually start. 18 * It will throw error if emnapi runs in browser main thread 19 * since browser disallow blocking the main thread (Atomics.wait). 20 * @defaultValue false 21 */ 22 waitThreadStart: isNode || (isBrowser && !isBrowserMainThread), 23 24 /** 25 * Reuse the thread worker after thread exit to avoid re-creatation 26 * @defaultValue false 27 */ 28 reuseWorker: { 29 /** 30 * @see {@link https://emscripten.org/docs/tools_reference/settings_reference.html#pthread-pool-size | PTHREAD_POOL_SIZE} 31 */ 32 size: 0, 33 34 /** 35 * @see {@link https://emscripten.org/docs/tools_reference/settings_reference.html#pthread-pool-size-strict | PTHREAD_POOL_SIZE_STRICT} 36 */ 37 strict: false 38 }, 39 40 onCreateWorker () { 41 return new Worker('./worker.js') 42 // Node.js 43 // const { Worker } = require('worker_threads') 44 // return new Worker(join(__dirname, './worker.js'), { 45 // env: process.env, 46 // execArgv: ['--experimental-wasi-unstable-preview1'] 47 // }) 48 }, 49 overwriteImports (importObject) { 50 importObject.env.memory = new WebAssembly.Memory({ 51 initial: 16777216 / 65536, 52 maximum: 2147483648 / 65536, 53 shared: true 54 }) 55 } 56})
1// worker.js 2(function () { 3 let fs, WASI, emnapiCore 4 5 const ENVIRONMENT_IS_NODE = 6 typeof process === 'object' && process !== null && 7 typeof process.versions === 'object' && process.versions !== null && 8 typeof process.versions.node === 'string' 9 10 if (ENVIRONMENT_IS_NODE) { 11 const nodeWorkerThreads = require('worker_threads') 12 13 const parentPort = nodeWorkerThreads.parentPort 14 15 parentPort.on('message', (data) => { 16 globalThis.onmessage({ data }) 17 }) 18 19 fs = require('fs') 20 21 Object.assign(globalThis, { 22 self: globalThis, 23 require, 24 Worker: nodeWorkerThreads.Worker, 25 importScripts: function (f) { 26 (0, eval)(fs.readFileSync(f, 'utf8') + '//# sourceURL=' + f) 27 }, 28 postMessage: function (msg) { 29 parentPort.postMessage(msg) 30 } 31 }) 32 33 WASI = require('wasi').WASI 34 emnapiCore = require('@emnapi/core') 35 } else { 36 importScripts('./node_modules/memfs-browser/dist/memfs.js') 37 importScripts('./node_modules/@tybys/wasm-util/dist/wasm-util.min.js') 38 importScripts('./node_modules/@emnapi/core/dist/emnapi-core.js') 39 emnapiCore = globalThis.emnapiCore 40 41 const { Volume, createFsFromVolume } = memfs 42 fs = createFsFromVolume(Volume.fromJSON({ 43 '/': null 44 })) 45 46 WASI = globalThis.wasmUtil.WASI 47 } 48 49 const { instantiateNapiModuleSync, MessageHandler } = emnapiCore 50 51 const handler = new MessageHandler({ 52 onLoad ({ wasmModule, wasmMemory }) { 53 const wasi = new WASI({ fs }) 54 55 return instantiateNapiModuleSync(wasmModule, { 56 childThread: true, 57 wasi, 58 overwriteImports (importObject) { 59 importObject.env.memory = wasmMemory 60 } 61 }) 62 } 63 }) 64 65 globalThis.onmessage = function (e) { 66 handler.handle(e) 67 // handle other messages 68 } 69})()
-DEMNAPI_WORKER_POOL_SIZE=4
This is UV_THREADPOOL_SIZE
equivalent at compile time, if not predefined, emnapi will read asyncWorkPoolSize
option or UV_THREADPOOL_SIZE
from Emscripten environment variable at runtime:
1Module.init({
2 // ...
3 asyncWorkPoolSize: 2
4})
5
6// if asyncWorkPoolSize is not specified
7Module.preRun = Module.preRun || [];
8Module.preRun.push(function () {
9 if (typeof ENV !== 'undefined') {
10 ENV.UV_THREADPOOL_SIZE = '2';
11 }
12});
1// wasi 2instantiateNapiModule({ 3 // ... 4 asyncWorkPoolSize: 2 5}) 6// if asyncWorkPoolSize is not specified 7new WASI({ 8 env: { 9 UV_THREADPOOL_SIZE: '2' 10 } 11})
It represent max of EMNAPI_WORKER_POOL_SIZE
async work (napi_queue_async_work
) can be executed in parallel. Default is not defined.
You can set both PTHREAD_POOL_SIZE
and EMNAPI_WORKER_POOL_SIZE
to number of CPU cores
in general.
If you use another library function which may create N
child threads in async work,
then you need to set PTHREAD_POOL_SIZE
to EMNAPI_WORKER_POOL_SIZE * (N + 1)
.
This option only has effect if you use -pthread
.
Emnapi will create EMNAPI_WORKER_POOL_SIZE
threads when initializing,
it will throw error if PTHREAD_POOL_SIZE < EMNAPI_WORKER_POOL_SIZE && PTHREAD_POOL_SIZE_STRICT == 2
.
See Issue #8 for more detail.
-DEMNAPI_NEXTTICK_TYPE=0
This option only has effect if you use -pthread
, Default is 0
.
Tell emnapi how to delay async work in uv_async_send
/ uv__async_close
.
0
: Use setImmediate()
(Node.js native setImmediate
or browser MessageChannel
and port.postMessage
)1
: Use Promise.resolve().then()
-DEMNAPI_USE_PROXYING=1
This option only has effect if you use emscripten -pthread
. Default is 1
if emscripten version >= 3.1.9
, else 0
.
0
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.
1
:
Use Emscripten proxying API 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.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
11 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
0 existing vulnerabilities detected
Reason
packaging workflow detected
Details
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
Project has not signed or included provenance with any releases.
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More