diff --git a/.gitignore b/.gitignore index ca66ca2d..8a7fbf41 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ /test/CMakefiles node_modules !example/node_modules -/include/node_api_full.h package-lock.json /example/build out diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 58412f2a..7fb86f0e 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,7 +3,7 @@ "includePath": [ "${default}", // "${env:USERPROFILE}/AppData/Local/node-gyp/Cache/16.15.0/include/node" - "${workspaceFolder}/packages/emnapi/include", + "${workspaceFolder}/packages/emnapi/include/node", "${workspaceFolder}/node_modules/node-addon-api" ], "defines": ["NAPI_DISABLE_CPP_EXCEPTIONS", "NODE_ADDON_API_ENABLE_MAYBE"], diff --git a/packages/emnapi/CMakeLists.txt b/packages/emnapi/CMakeLists.txt index 9b6de682..773c9cc7 100644 --- a/packages/emnapi/CMakeLists.txt +++ b/packages/emnapi/CMakeLists.txt @@ -38,7 +38,7 @@ set(EMNAPI_THREADS_SRC ) set(EMNAPI_SRC ${ENAPI_BASIC_SRC} ${EMNAPI_THREADS_SRC}) -set(EMNAPI_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/include") +set(EMNAPI_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/include/node") set(EMNAPI_DEFINES "BUILDING_NODE_EXTENSION") set(EMNAPI_JS_LIB "${CMAKE_CURRENT_SOURCE_DIR}/dist/library_napi.js") @@ -209,35 +209,41 @@ if(LIB_ARCH) endif() install(FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/emnapi_common.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/emnapi.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/js_native_api_types.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/js_native_api.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/node_api_types.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/node_api.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/uv.h - DESTINATION "include/${PROJECT_NAME}") + ${CMAKE_CURRENT_SOURCE_DIR}/common.gypi + ${CMAKE_CURRENT_SOURCE_DIR}/emnapi.gyp + DESTINATION ".") + +install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/emnapi_common.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/emnapi.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/js_native_api_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/js_native_api.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/node_api_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/node_api.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/uv.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/config.gypi + DESTINATION "include/node") install(DIRECTORY - ${CMAKE_CURRENT_SOURCE_DIR}/include/uv - DESTINATION "include/${PROJECT_NAME}") + ${CMAKE_CURRENT_SOURCE_DIR}/include/node/uv + DESTINATION "include/node") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dist/library_napi.js - DESTINATION "lib/${PROJECT_NAME}") + DESTINATION "dist") if(EMNAPI_INSTALL_SRC) install(FILES ${EMNAPI_SRC} "${CMAKE_CURRENT_SOURCE_DIR}/src/emnapi_internal.h" - DESTINATION "src/${PROJECT_NAME}") + DESTINATION "src") install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/uv - DESTINATION "src/${PROJECT_NAME}") + DESTINATION "src") install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/malloc - DESTINATION "src/${PROJECT_NAME}") + DESTINATION "src") install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/thread - DESTINATION "src/${PROJECT_NAME}") + DESTINATION "src") endif() diff --git a/packages/emnapi/README.md b/packages/emnapi/README.md index cad4b150..9bd3f2c9 100644 --- a/packages/emnapi/README.md +++ b/packages/emnapi/README.md @@ -177,7 +177,7 @@ module.exports = (function (exports) { emcc -O3 \ -DBUILDING_NODE_EXTENSION \ "-DNAPI_EXTERN=__attribute__((__import_module__(\"env\")))" \ - -I./node_modules/emnapi/include \ + -I./node_modules/emnapi/include/node \ -L./node_modules/emnapi/lib/wasm32-emscripten \ --js-library=./node_modules/emnapi/dist/library_napi.js \ -sEXPORTED_FUNCTIONS="['_malloc','_free','_napi_register_wasm_v1','_node_api_module_get_api_version_v1']" \ @@ -194,7 +194,7 @@ emcc -O3 \ ```bash clang -O3 \ -DBUILDING_NODE_EXTENSION \ - -I./node_modules/emnapi/include \ + -I./node_modules/emnapi/include/node \ -L./node_modules/emnapi/lib/wasm32-wasi \ --target=wasm32-wasi \ --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \ @@ -222,7 +222,7 @@ Choose `libdlmalloc.a` or `libemmalloc.a` for `malloc` and `free`. ```bash clang -O3 \ -DBUILDING_NODE_EXTENSION \ - -I./node_modules/emnapi/include \ + -I./node_modules/emnapi/include/node \ -L./node_modules/emnapi/lib/wasm32 \ --target=wasm32 \ -nostdlib \ @@ -466,7 +466,7 @@ em++ -O3 \ "-DNAPI_EXTERN=__attribute__((__import_module__(\"env\")))" \ -DNAPI_DISABLE_CPP_EXCEPTIONS \ -DNODE_ADDON_API_ENABLE_MAYBE \ - -I./node_modules/emnapi/include \ + -I./node_modules/emnapi/include/node \ -I./node_modules/node-addon-api \ -L./node_modules/emnapi/lib/wasm32-emscripten \ --js-library=./node_modules/emnapi/dist/library_napi.js \ @@ -486,7 +486,7 @@ clang++ -O3 \ -DBUILDING_NODE_EXTENSION \ -DNAPI_DISABLE_CPP_EXCEPTIONS \ -DNODE_ADDON_API_ENABLE_MAYBE \ - -I./node_modules/emnapi/include \ + -I./node_modules/emnapi/include/node \ -I./node_modules/node-addon-api \ -L./node_modules/emnapi/lib/wasm32-wasi \ --target=wasm32-wasi \ @@ -518,7 +518,7 @@ You can still use `wasm32-unknown-unknown` target if you use Node-API C API only ```bash clang++ -O3 \ -DBUILDING_NODE_EXTENSION \ - -I./node_modules/emnapi/include \ + -I./node_modules/emnapi/include/node \ -L./node_modules/emnapi/lib/wasm32 \ --target=wasm32 \ -fno-exceptions \ @@ -625,158 +625,166 @@ cmake --build build Output code can run in recent version modern browsers and Node.js latest LTS. IE is not supported. -### Using Rust (Experimental) +### Using node-gyp (Experimental) -Currently you can use [napi-rs](https://github.com/napi-rs/napi-rs) like this, more work is working in progress. +Currently node-gyp works on Linux only and don't support static library linking in cross-compiling. +There are opened PRs to try to make node-gyp work fine. -Note: WASI target require rust nightly toolchain. +- https://github.com/nodejs/gyp-next/pull/222 +- https://github.com/nodejs/node-gyp/pull/2974 -
-Cargo.toml
- -```toml -[package] -edition = "2021" -name = "binding" -version = "0.0.0" - -# We should build binary for WASI reactor -# https://github.com/rust-lang/rust/pull/79997 -# https://github.com/WebAssembly/WASI/issues/24 -# for wasm -[[bin]] -name = "binding" -path = "src/main.rs" - -# for native -# [lib] -# name = "binding" -# path = "src/lib.rs" -# crate-type = ["cdylib"] - -[dependencies] -napi = { version = "2.12.1", default-features = false, features = ["napi8"] } -napi-sys = { version = "2.2.3", features = ["napi8"] } -napi-derive = "2.12.2" - -[build-dependencies] -napi-build = "2.0.1" - -[profile.release] -strip = "symbols" -``` +If you experienced issues on Windows or macOS, please check the PRs for upstream changes detail and see +[emnapi-node-gyp-test](https://github.com/toyobayashi/emnapi-node-gyp-test) for examples. -
+- Variables -
-.cargo/config.toml
- -```toml -[build] -target = [ - "wasm32-unknown-unknown", - "wasm32-wasi" -] - -[target.wasm32-unknown-unknown] -rustflags = [ - "-L./node_modules/emnapi/lib/wasm32", - "-lemnapi", - "-ldlmalloc", - # "-lemmalloc", - "-C", "link-arg=--no-entry", - "-C", "link-arg=--initial-memory=16777216", - "-C", "link-arg=--export-dynamic", - "-C", "link-arg=--export=malloc", - "-C", "link-arg=--export=free", - "-C", "link-arg=--export=napi_register_wasm_v1", - "-C", "link-arg=--export-if-defined=node_api_module_get_api_version_v1", - "-C", "link-arg=--export-table", - "-C", "link-arg=--import-undefined", -] - -[target.wasm32-wasi] -rustflags = [ - "-L./node_modules/emnapi/lib/wasm32-wasi", - "-lemnapi", - "-C", "link-arg=--initial-memory=16777216", - "-C", "link-arg=--export-dynamic", - "-C", "link-arg=--export=malloc", - "-C", "link-arg=--export=free", - "-C", "link-arg=--export=napi_register_wasm_v1", - "-C", "link-arg=--export-if-defined=node_api_module_get_api_version_v1", - "-C", "link-arg=--export-table", - "-C", "link-arg=--import-undefined", - "-Z", "wasi-exec-model=reactor", # +nightly -] -``` +Arch: `node-gyp configure --arch=` -
+```ts +// node-gyp configure -- -Dvariable_name=value -
-src/main.rs
+declare var OS: 'emscripten' | 'wasi' | 'unknown' | '' -```rust -#![no_main] +/** + * Enable async work and threadsafe-functions + * @default 0 + */ +declare var wasm_threads: 0 | 1 -use napi_derive::napi; +/** @default 1048576 */ +declare var stack_size: number -#[napi] -fn fibonacci(n: u32) -> u32 { - match n { - 1 | 2 => 1, - _ => fibonacci(n - 1) + fibonacci(n - 2), - } +/** @default 16777216 */ +declare var initial_memory: number + +/** @default 2147483648 */ +declare var max_memory: number + +/** @default path.join(path.dirname(commonGypiPath,'./dist/library_napi.js')) */ +declare var emnapi_js_library: string + +/** @default 0 */ +declare var emnapi_manual_linking: 0 | 1 +``` + +- Create `binding.gyp` + + +```py +{ + "targets": [ + { + "target_name": "hello", + "sources": [ + "hello.c" + ], + "conditions": [ + ["OS == 'emscripten'", { + "product_extension": "js", # required + + # Windows and Linux + "cflags": [], + "cflags_c": [], + "cflags_cc": [], + "ldflags": [], + + # macOS uses following config + 'xcode_settings': { + "WARNING_CFLAGS": [], # cflags + "OTHER_CFLAGS": [], # cflags_c + "OTHER_CPLUSPLUSFLAGS": [], # cflags_cc + "OTHER_LDFLAGS": [] # ldflags + } + }], + ["OS == 'wasi'", { + # ... + }], + ["OS == 'unknown' or OS == ''", { + # ... + }] + ] + } + ] } ``` -
+- Add the following environment variables. -
-index.js
+```bash +# Linux or macOS +export GYP_CROSSCOMPILE=1 -```js -const fs = require('fs') -const path = require('path') -const useWASI = false +# emscripten +export AR_target="$EMSDK/upstream/emscripten/emar" +export CC_target="$EMSDK/upstream/emscripten/emcc" +export CXX_target="$EMSDK/upstream/emscripten/em++" -let wasi -if (useWASI) { - const { WASI } = require('wasi') - wasi = new WASI({ /* ... */ }) -} +# wasi-sdk +export AR_target="$WASI_SDK_PATH/bin/ar" +export CC_target="$WASI_SDK_PATH/bin/clang" +export CXX_target="$WASI_SDK_PATH/bin/clang++" +``` -const { instantiateNapiModule } = require('@emnapi/core') +```bat +@REM Windows -const wasmBuffer = useWASI - ? fs.readFileSync(path.join(__dirname, './target/wasm32-wasi/release/binding.wasm')) - : fs.readFileSync(path.join(__dirname, './target/wasm32-unknown-unknown/release/binding.wasm')) - -instantiateNapiModule(wasmBuffer, { - context: require('@emnapi/runtime').getDefaultContext(), - wasi, - beforeInit ({ instance }) { - for (const sym in instance.exports) { - if (sym.startsWith('__napi_register__')) { - instance.exports[sym]() - } - } - }, - overwriteImports (importObject) { - importObject.env = { - ...importObject.env, - ...importObject.napi, - ...importObject.emnapi - } - } -}).then(({ napiModule }) => { - const binding = napiModule.exports - // output: 5 - console.log(binding.fibonacci(5)) -}) +set GYP_CROSSCOMPILE=1 + +@REM emscripten +call set AR_target=%%EMSDK:\=/%%/upstream/emscripten/emar.bat +call set CC_target=%%EMSDK:\=/%%/upstream/emscripten/emcc.bat +call set CXX_target=%%EMSDK:\=/%%/upstream/emscripten/em++.bat + +@REM wasi-sdk +call set AR_target=%%WASI_SDK_PATH:\=/%%/bin/ar.exe +call set CC_target=%%WASI_SDK_PATH:\=/%%/bin/clang.exe +call set CXX_target=%%WASI_SDK_PATH:\=/%%/bin/clang++.exe ``` -
+- Build + +```bash +# Linux or macOS + +# emscripten +emmake node-gyp rebuild \ + --arch=wasm32 \ + --nodedir=./node_modules/emnapi \ + -- -f make -DOS=emscripten # -Dwasm_threads=1 + +# wasi +node-gyp rebuild \ + --arch=wasm32 \ + --nodedir=./node_modules/emnapi \ + -- -f make -DOS=wasi # -Dwasm_threads=1 + +# bare wasm32 +node-gyp rebuild \ + --arch=wasm32 \ + --nodedir=./node_modules/emnapi \ + -- -f make -DOS=unknown # -Dwasm_threads=1 +``` + +```bat +@REM Use make generator on Windows +@REM Run the bat file in POSIX-like environment (e.g. Cygwin) + +@REM emscripten +call npx.cmd node-gyp configure --arch=wasm32 --nodedir=./node_modules/emnapi -- -f make -DOS=emscripten +call emmake.bat make -C %~dp0build + +@REM wasi +call npx.cmd node-gyp configure --arch=wasm32 --nodedir=./node_modules/emnapi -- -f make -DOS=wasi +make -C %~dp0build + +@REM bare wasm32 +call npx.cmd node-gyp configure --arch=wasm32 --nodedir=./node_modules/emnapi -- -f make -DOS=unknown +make -C %~dp0build +``` + +### Using Rust + +See [napi-rs](https://github.com/napi-rs/napi-rs) ### Multithread @@ -856,7 +864,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") "-sDEFAULT_PTHREAD_STACK_SIZE=2MB" ) elseif(CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-wasi-threads") - # Experimental target_link_libraries(hello emnapi-mt) set_target_properties(hello PROPERTIES SUFFIX ".wasm") target_compile_options(hello PRIVATE "-fno-exceptions" "-pthread") @@ -897,7 +904,7 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release \ -DEMNAPI_WORKER_POOL_SIZE=4 \ -G Ninja -H. -Bbuild -# wasi-sdk with thread support (Experimental) +# wasi-sdk with thread support cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk-pthread.cmake \ -DWASI_SDK_PREFIX=$WASI_SDK_PATH \ -DEMNAPI_FIND_NODE_ADDON_API=ON \ diff --git a/packages/emnapi/common.gypi b/packages/emnapi/common.gypi new file mode 100644 index 00000000..26909569 --- /dev/null +++ b/packages/emnapi/common.gypi @@ -0,0 +1,423 @@ +# This file is originally created by [RReverser](https://github.com/RReverser) +# in https://github.com/lovell/sharp/pull/3522 +{ + 'variables': { + # OS: 'emscripten' | 'wasi' | 'unknown' + 'clang': 1, + 'target_arch%': 'wasm32', + 'wasm_threads%': 0, + 'stack_size%': 1048576, + 'initial_memory%': 16777216, + 'max_memory%': 2147483648, + # must be an absolute path + 'emnapi_js_library%': ' {